#Einstellungen zur besseren Lesbarkeit des Outputs
options(scipen = 999)
options(digits = 3)
#Laden der Pakete
library(tidyverse)
library(report) #Paket für die übersichtlichere Darstellung von Ergebnissen
#Laden der Datensätze
data_situation <- readxl::read_excel("data/data_esm.xlsx",
col_names = TRUE)
data_person_presurvey <- readxl::read_excel("data/data_person_level_presurvey.xlsx",
col_names = TRUE)
data_person_postsurvey <- readxl::read_excel("data/data_person_level_postsurvey.xlsx",
col_names = TRUE)B — Dateninspektion, -prüfung und -bereinigung
Wir starten in diesem Abschnitt nun damit, uns mit unseren Daten vertraut zu machen und diese ggf. zu bereinigen.
Wir laden zunächst das tidyverse sowie das report-Paket. Weiterhin laden wir unsere Datensätze auf Situations- und Personenebene. Im Fall unserer Beispielstudie sind das auf Personenebene je ein Datensatz aus der Vorab- und aus der Nachbefragung. Da der Fokus auf den Situationsdaten liegt, nutzen wir im Folgenden aus den Personendatensätzen nur die Variablen, die wir im Weiteren verwenden werden und spielen die bereits bereinigten und aufbereiteten Personendatensätze ein. Diese müssen – wie jeder Befragungsdatensatz – selbstverständlich vorher ebenfalls bereinigt und aufbereitet werden.
B.1 Schritt 1 - Grundlegende Checks
Für diesen ersten Schritt benötigen wir nur den In-situ-Datensatz. Die Personendatensätze beziehen wir ab Schritt 2 mit ein.
B.1.1 Fallzahlen
Zunächst schauen wir, wie viele Personen wie viele Protokolle ausgefüllt haben.
#Fälle auf Situations- und Personenebene
sum_protokolle <- as_tibble(data_situation |>
group_by(id) |>
count())
sum_protokolle |>
select(n) |>
map_dfr(~ tibble
( Personen = length(.x),
Situationen = sum(.x, na.rm = TRUE)),
.id = "variable")Wir sehen, dass wir von 174 Personen insgesamt 3654 Protokolle gesammelt haben.
Hinweis: In diesem Datensatz gibt es nur Zeilen für ausgefüllte Protokolle. Haben wir auch Zeilen für verpasste Protokolle, müssen wir zur Bestimmung der Fallzahl auf Situationsebene vorher die Zeilen filtern, die ausgefüllte Protokolle enthalten.
B.1.2 Ausfülldauer
Nun schauen wir, wie lange das Ausfüllen der Protokolle im Schnitt gedauert hat. Die Angabe liegt im Datensatz in Sekunden vor, wir rechnen in Minuten um.
data_situation |>
select(ausfuelldauer) |>
map_dfr(~ tibble
(MW = (mean(.x, na.rm = TRUE)/60),
SD = (sd(.x, na.rm = TRUE)/60),
Min = (min(.x, na.rm = TRUE)/60),
Max = (max(.x, na.rm = TRUE)/60)),
.id = "variable")Im Schnitt haben die Teilnehmer:innen also etwas mehr als eine halbe Minute gebraucht, maximal etwas über 3 Minuten.
B.1.3 Vollständigkeit und Benennung der Variablen
Nun verschaffen wir uns einen Überblick über die Variablen im Datensatz.
glimpse(data_situation)Rows: 3,654
Columns: 24
$ id <chr> "12ASDZ9VKS", "12ASDZ9VKS", "12ASDZ9VKS", "12ASDZ9VKS", …
$ datum <chr> "2023-09-18", "2023-09-18", "2023-09-18", "2023-09-19", …
$ uhrzeit <chr> "16:36:16", "19:34:08", "21:36:51", "09:14:22", "17:30:4…
$ studientag <dbl> 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 7, 8, 8,…
$ protokoll_tag <dbl> 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 1, 1, 2,…
$ protokoll_ges <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1…
$ ausfuelldauer <dbl> 92, 20, 99, 23, 60, 59, 39, 20, 16, 39, 33, 18, 79, 52, …
$ is01 <chr> "Ja, True-Crime-Podcast", "Nein, kein Podcast", "Ja, Tru…
$ is12 <chr> "> 2 bis 4 Stunden", NA, "0 bis 1 Stunde", NA, "> 1 bis …
$ is02_01 <dbl> 0, NA, 1, NA, 1, 0, 0, NA, NA, NA, 1, NA, 1, 4, NA, NA, …
$ is02_02 <dbl> 45, NA, 12, NA, 42, 40, 27, NA, NA, NA, 15, NA, 50, 50, …
$ is05 <chr> "Nein", NA, "Nein", NA, "Nein", "Ja, herkoemmliche Kopfh…
$ is06 <chr> "Zuhause", NA, "Zuhause", NA, "Zuhause", "Unterwegs", "U…
$ is07 <chr> "Hausarbeit", NA, "Andere Beschaeftigung", NA, "Koerperp…
$ is11 <dbl> 3, NA, 5, NA, 5, 4, 4, NA, NA, NA, 4, NA, 4, 4, NA, NA, …
$ is08_01 <dbl> 2, NA, 4, NA, 2, 3, NA, NA, NA, NA, NA, NA, 2, 4, NA, NA…
$ is08_02 <dbl> 2, NA, 5, NA, 5, 5, NA, NA, NA, NA, NA, NA, 4, 4, NA, NA…
$ is08_03 <dbl> 3, NA, 5, NA, 4, 5, NA, NA, NA, NA, NA, NA, 4, 4, NA, NA…
$ is08_04 <dbl> 4, NA, 4, NA, 4, 4, NA, NA, NA, NA, NA, NA, 2, 3, NA, NA…
$ is08_05 <dbl> 2, NA, 3, NA, 5, 4, NA, NA, NA, NA, NA, NA, 4, 4, NA, NA…
$ is08_06 <dbl> 4, NA, 3, NA, 5, 4, NA, NA, NA, NA, NA, NA, 3, 4, NA, NA…
$ is16 <dbl> 3, 4, 4, 4, 3, 4, 4, 5, 3, 4, 3, 3, 3, 4, 5, 3, 3, 3, 4,…
$ is14 <chr> NA, "Essen", NA, "Sport", NA, NA, NA, "Arbeit (inkl. Sch…
$ is15 <chr> NA, "Zuhause", NA, "Zuhause", NA, NA, NA, "Zuhause", "Zu…Wir sehen, dass wir insgesamt 24 Variablen haben. Die Namen sind z. T. nicht selbsterklärend, deshalb benennen wir die Variablen sinnvoll um (siehe Fragebogen hier).
data_situation <- data_situation |>
rename(pod_nutz = is01,
pod_latency = is12,
pod_dauer_std = is02_01,
pod_dauer_min = is02_02,
kopfhoerer = is05,
umgebung = is06,
parallel = is07,
aufmerksamkeit = is11,
narr_trans_01 = is08_01,
narr_trans_02 = is08_02,
narr_trans_03 = is08_03,
narr_trans_04 = is08_04,
narr_trans_05 = is08_05,
narr_trans_06 = is08_06,
stimmung = is16,
nichtnutzer_taetigkeit = is14,
nichtnutzer_umgebung = is15)
glimpse(data_situation)Rows: 3,654
Columns: 24
$ id <chr> "12ASDZ9VKS", "12ASDZ9VKS", "12ASDZ9VKS", "12AS…
$ datum <chr> "2023-09-18", "2023-09-18", "2023-09-18", "2023…
$ uhrzeit <chr> "16:36:16", "19:34:08", "21:36:51", "09:14:22",…
$ studientag <dbl> 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6,…
$ protokoll_tag <dbl> 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1,…
$ protokoll_ges <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, …
$ ausfuelldauer <dbl> 92, 20, 99, 23, 60, 59, 39, 20, 16, 39, 33, 18,…
$ pod_nutz <chr> "Ja, True-Crime-Podcast", "Nein, kein Podcast",…
$ pod_latency <chr> "> 2 bis 4 Stunden", NA, "0 bis 1 Stunde", NA, …
$ pod_dauer_std <dbl> 0, NA, 1, NA, 1, 0, 0, NA, NA, NA, 1, NA, 1, 4,…
$ pod_dauer_min <dbl> 45, NA, 12, NA, 42, 40, 27, NA, NA, NA, 15, NA,…
$ kopfhoerer <chr> "Nein", NA, "Nein", NA, "Nein", "Ja, herkoemmli…
$ umgebung <chr> "Zuhause", NA, "Zuhause", NA, "Zuhause", "Unter…
$ parallel <chr> "Hausarbeit", NA, "Andere Beschaeftigung", NA, …
$ aufmerksamkeit <dbl> 3, NA, 5, NA, 5, 4, 4, NA, NA, NA, 4, NA, 4, 4,…
$ narr_trans_01 <dbl> 2, NA, 4, NA, 2, 3, NA, NA, NA, NA, NA, NA, 2, …
$ narr_trans_02 <dbl> 2, NA, 5, NA, 5, 5, NA, NA, NA, NA, NA, NA, 4, …
$ narr_trans_03 <dbl> 3, NA, 5, NA, 4, 5, NA, NA, NA, NA, NA, NA, 4, …
$ narr_trans_04 <dbl> 4, NA, 4, NA, 4, 4, NA, NA, NA, NA, NA, NA, 2, …
$ narr_trans_05 <dbl> 2, NA, 3, NA, 5, 4, NA, NA, NA, NA, NA, NA, 4, …
$ narr_trans_06 <dbl> 4, NA, 3, NA, 5, 4, NA, NA, NA, NA, NA, NA, 3, …
$ stimmung <dbl> 3, 4, 4, 4, 3, 4, 4, 5, 3, 4, 3, 3, 3, 4, 5, 3,…
$ nichtnutzer_taetigkeit <chr> NA, "Essen", NA, "Sport", NA, NA, NA, "Arbeit (…
$ nichtnutzer_umgebung <chr> NA, "Zuhause", NA, "Zuhause", NA, NA, NA, "Zuha…Jetzt sind die Variablennamen verständlicher. Das erleichtert das weitere Arbeiten mit dem Datensatz.
B.1.4 Sinnhaftes Ausfüllen
Nun prüfen wir, ob alle Variablen ausgefüllt sind, die ausgefüllt sein sollen und ob es in bestimmten Variablen viele fehlende Werte gibt.
data_situation |>
select(-(id:ausfuelldauer)) |>
map_dfr(~ tibble
(ausgefuellt = sum(!is.na(.x)),
fehlend = sum(is.na(.x))),
.id = "variable") |>
print(n = Inf) #stellt bei vielen Variablen sicher, dass alle angezeigt werden# A tibble: 17 × 3
variable ausgefuellt fehlend
<chr> <int> <int>
1 pod_nutz 3654 0
2 pod_latency 1660 1994
3 pod_dauer_std 1641 2013
4 pod_dauer_min 1648 2006
5 kopfhoerer 1655 1999
6 umgebung 1652 2002
7 parallel 1652 2002
8 aufmerksamkeit 1651 2003
9 narr_trans_01 1172 2482
10 narr_trans_02 1169 2485
11 narr_trans_03 1168 2486
12 narr_trans_04 1167 2487
13 narr_trans_05 1171 2483
14 narr_trans_06 1172 2482
15 stimmung 3633 21
16 nichtnutzer_taetigkeit 1986 1668
17 nichtnutzer_umgebung 1988 1666Wir sehen, dass lediglich die Variable pod_nutz keine, alle anderen Variablen allerdings viele fehlende Werte aufweisen. Ein Blick in den Fragebogen hier verrät uns, dass dies an der Filterführung liegt. Teilnehmer:innen, die keinen Podcast gehört haben, haben lediglich die Fragen zu ihrer Stimmung (stimmung), der Tätigkeit, der sie gerade nachgehen (nichtnutzer_taetigkeit) und ihrer Umgebung (nichtnutzer_umgebung) beantwortet. Diejenigen, die zwar einen Podcast gehört haben, aber nicht aus dem Genre True Crime, haben die Fragen zur narrativen Transportation (narr_trans_01 bis narr_trans_06) nicht beantwortet.
Deshalb ergibt es Sinn, nun zu prüfen, ob jeweils die Variablen ausgefüllt sind, die je nach Filterführung ausgefüllt sein sollten. Zunächst betrachten wir nur die Messzeitpunkte (d. h. Fälle), in denen kein Podcast gehört wurde.
data_situation |>
filter(pod_nutz == "Nein, kein Podcast") |>
select(-(id:ausfuelldauer)) |>
map_dfr(~ tibble
(ausgefuellt = sum(!is.na(.x)),
fehlend = sum(is.na(.x))),
.id = "variable")|>
print(n = Inf) #stellt bei vielen Variablen sicher, dass alle angezeigt werden# A tibble: 17 × 3
variable ausgefuellt fehlend
<chr> <int> <int>
1 pod_nutz 1991 0
2 pod_latency 0 1991
3 pod_dauer_std 0 1991
4 pod_dauer_min 0 1991
5 kopfhoerer 0 1991
6 umgebung 0 1991
7 parallel 0 1991
8 aufmerksamkeit 0 1991
9 narr_trans_01 0 1991
10 narr_trans_02 0 1991
11 narr_trans_03 0 1991
12 narr_trans_04 0 1991
13 narr_trans_05 0 1991
14 narr_trans_06 0 1991
15 stimmung 1987 4
16 nichtnutzer_taetigkeit 1986 5
17 nichtnutzer_umgebung 1988 3Das sieht gut aus: Die Fragen, die in diesen Protokollen nicht beantwortet werden sollten, sind leer und es gibt nur noch einzelne fehlende Werte bei stimmung, nichtnutzer_taetigkeit und nichtnutzer_umgebung.
Nun betrachten wir nur die Messzeitpunkte, in denen Podcast gehört wurde, aber kein True-Crime-Podcast.
data_situation |>
filter(pod_nutz == "Ja, anderer Podcast") |>
select(-(id:ausfuelldauer)) |>
map_dfr(~ tibble
(ausgefuellt = sum(!is.na(.x)),
fehlend = sum(is.na(.x))),
.id = "variable") |>
print(n = Inf) #stellt bei vielen Variablen sicher, dass alle angezeigt werden# A tibble: 17 × 3
variable ausgefuellt fehlend
<chr> <int> <int>
1 pod_nutz 479 0
2 pod_latency 478 1
3 pod_dauer_std 471 8
4 pod_dauer_min 475 4
5 kopfhoerer 476 3
6 umgebung 475 4
7 parallel 475 4
8 aufmerksamkeit 475 4
9 narr_trans_01 0 479
10 narr_trans_02 0 479
11 narr_trans_03 0 479
12 narr_trans_04 0 479
13 narr_trans_05 0 479
14 narr_trans_06 0 479
15 stimmung 474 5
16 nichtnutzer_taetigkeit 0 479
17 nichtnutzer_umgebung 0 479Auch hier erscheint alles plausibel: Die Fragen, die in diesen Protokollen nicht beantwortet werden sollten, sind leer und es gibt nur noch einzelne fehlende Werte bei den Variablen, die ausgefüllt sein sollten.
Schließlich schauen wir uns die Messzeitpunkte der True-Crime-Podcast-Hörer:innen an.
data_situation |>
filter(pod_nutz == "Ja, True-Crime-Podcast") |>
select(-(id:ausfuelldauer)) |>
map_dfr(~ tibble
(ausgefuellt = sum(!is.na(.x)),
fehlend = sum(is.na(.x))),
.id = "variable") |>
print(n = Inf) #stellt bei vielen Variablen sicher, dass alle angezeigt werden# A tibble: 17 × 3
variable ausgefuellt fehlend
<chr> <int> <int>
1 pod_nutz 1184 0
2 pod_latency 1182 2
3 pod_dauer_std 1170 14
4 pod_dauer_min 1173 11
5 kopfhoerer 1179 5
6 umgebung 1177 7
7 parallel 1177 7
8 aufmerksamkeit 1176 8
9 narr_trans_01 1172 12
10 narr_trans_02 1169 15
11 narr_trans_03 1168 16
12 narr_trans_04 1167 17
13 narr_trans_05 1171 13
14 narr_trans_06 1172 12
15 stimmung 1172 12
16 nichtnutzer_taetigkeit 0 1184
17 nichtnutzer_umgebung 0 1184Auch hier gilt: Nur die Variablen, die in diesen Protokollen ohnehin nicht beantworten werden mussten, weisen viele fehlende Werte auf.
B.1.5 Verteilung der Variablen
Im nächsten Schritt wollen wir prüfen, ob alle Werte plausibel sind, also nur solche Werte vorkommen, die vorkommen sollen und uns dabei auch die Verteilung der Variablen anschauen.
Zunächst die metrischen Variablen: Hier bietet sich ein Blick auf Mittelwert, Standardabweichung, Minimum und Maximum an.
data_situation |>
select(pod_dauer_std, pod_dauer_min, aufmerksamkeit, narr_trans_01:narr_trans_06, stimmung) |>
map_dfr(~ tibble
(M = mean(.x, na.rm = TRUE),
SD = sd(.x, na.rm = TRUE),
Min = min(.x, na.rm = TRUE),
Max = max(.x, na.rm = TRUE)),
.id = "variable")Alle Werte sehen weitgehend plausibel aus (auch wenn uns 10 Stunden Podcastnutzung vielleicht sehr lange erscheinen), bei den Skalenfragen erscheinen nur Werte im Wertebereich 1-5. Hier würden wir erkennen, ob fehlende Werte z.B. mit -9 oder 99 gekennzeichnet sind, was wir vor der weiteren Datensauswertung korrigieren und in NA umcodieren müssten.
Auch eine Visualisierung kann hier nützlich sein.
#zunächst müssen wir hierfür den Datensatz umstrukturieren
data_long <- gather(data_situation, key = "Variable", value = "Wert", aufmerksamkeit, narr_trans_01:narr_trans_06, stimmung)
#Dann können wir uns die Histogramme ausgeben lassen
ggplot(data_long, aes(x = Wert)) +
geom_histogram() +
facet_wrap(~ Variable, ncol = 3) +
labs(x = "Wert", y = "Häufigkeit", title = "Verteilung der Variablen") +
theme_bw()
Nun schauen wir die kategorialen Variablen an.
data_situation |>
count(pod_nutz) data_situation |>
count(pod_latency)data_situation |>
count(kopfhoerer)data_situation |>
count(umgebung)data_situation |>
count(parallel)data_situation |>
count(nichtnutzer_taetigkeit)data_situation |>
count(nichtnutzer_umgebung)Auch hier tauchen keine Antworten auf, die nicht vorgesehen waren.
B.2 Schritt 2 – Zentrale Indikatoren für die Teilnahmequalität
Zunächst schauen wir uns an, wie viele Personen unsere Studie abgebrochen haben. Welche Kriterien wir hierfür anlegen, kann unterschiedlich sein (siehe Abschnitt 9.2.1). Wir legen beispielhaft fest, dass wir alle Personen als Abbrecher:innen betrachten, die (1) in der Vorabbefragung ihre Zustimmung zur Teilnahme an der ESM-Studie gegeben haben, aber nicht mindestens drei Protokolle ausgefüllt haben und (2) zwar mindestens drei Protokolle ausgefüllt haben, aber nicht die Nachbefragung.
Wir betrachten zunächst, wie viele Personen ihr Einverständnis gegeben haben und wie viele nicht.
data_person_presurvey |>
count(consent_esm)Von den 256 Personen, die die Vorabbefragung komplett ausgefüllt haben, haben 196 ihr Einverständnis gegeben, auch an der ESM-Phase der Studie teilzunehmen (es war im Rahmen der hier vorgestellten Studie auch möglich, nur die Vorabbefragung auszufüllen), 60 Personen nicht.
Nun wollen wir wissen, wie viele von denen, die ihr Einverständnis gegeben haben, mindestens drei ESM-Protokolle ausgefüllt haben.
Hierfür erzeugen wir zunächst einen Datensatz, in dem für jede Person (id) angegeben ist, wie viele Protokolle sie in der ESM-Phase ausgefüllt hat. Diese Information spielen wir an den Datensatz der Vorabbefragung an.
anzahl_esm <- data_situation |>
group_by(id) |>
count()
attrition <- left_join(data_person_presurvey, anzahl_esm, by="id")
attrition <- attrition |>
filter(consent_esm == "eingewilligt") |> #wir behalten nur die, die ihr Einverständnis gegeben haben
mutate(anz_esm_3 = if_else(is.na(n) | n < 3, "weniger als 3 Protokolle", "mind. 3 Protokolle"))
attrition |>
count(anz_esm_3)Wir sehen, dass 40 Personen nicht mindestens drei Protokolle ausgefüllt haben.
Nun schauen wir uns an, wie viele Personen an der Nachbefragung teilgenommen haben. Hierfür spielen wir zunächst den Datensatz der Nachbefragung an. Dann sehen wir, wie viele Personen (nicht) die nötige Anzahl an ESM-Protokollen und/oder die Nachbefragung ausgefüllt haben.
attrition_post <- left_join(attrition, data_person_postsurvey, by="id")
attrition_post <- attrition_post |>
mutate(tn_postsurvey = if_else(is.na(trans_scale), "nein", "ja")) #Daran, ob diese Variable ausgefüllt ist, erkennen wir, ob eine Person an der Nachbefragung teilgenommen hat
attrition_post |>
select(anz_esm_3, tn_postsurvey) |>
table() tn_postsurvey
anz_esm_3 ja nein
mind. 3 Protokolle 101 55
weniger als 3 Protokolle 2 38Wir sehen, dass 101 (von 196, die ihr Einverständnis gegeben haben) Personen sowohl die nötige Anzahl an Protokollen als auch die Nachbefragung ausgefüllt haben. Damit liegt unsere Abbruch-Quote bei 48%. In Abschnitt B.3 wollen wir uns noch anschauen, wie sich die Personen, die unsere Studie erfolgreich abgeschlossen haben, von den Abbrecher:innen unterscheiden.
Nun betrachten wir die Compliance der Teilnehmer:innen. Hierfür fokussieren wir uns auf die 101 Teilnehmer:innen, die nach unserer Definition die Studie abgeschlossen haben.
Dafür müssen wir diese Information zunächst an den In-situ-Datensatz anspielen.
attrition_post_anspiel <- attrition_post |>
mutate(abgeschlossen = if_else(anz_esm_3 == "mind. 3 Protokolle" & tn_postsurvey == "ja", "ja", "nein")) |>
select(id, abgeschlossen)
data_situation_ab <- left_join(data_situation, attrition_post_anspiel, by="id") |>
filter(abgeschlossen == "ja")Dann können wir die Compliance für jede:n Teilnehmer:in pro Studientag berechnen.
Hinweis: Wir könnten dies auch für die gesamte Feldzeit machen, da wir aber in Abschnitt B.3 noch die Compliance im Studienverlauf betrachten wollen, berechnen wir hier die Compliance für die einzelnen Tage.
#Compliance pro Teilnehmer:in und Studientag
compliance <- data_situation_ab |>
group_by(id, studientag) |>
count() |>
mutate(compliance = (n/4)*100) |> #es wurden 4 Signale pro Tag versendet
ungroup()
compliance <- compliance |>
complete(id, studientag = 1:12, fill = list(n = 0, compliance = 0)) #da in diesem Datensatz alle Tage fehlen, an denen der:die Teilnehmer:in gar kein Protokoll ausgefüllt hat, ergänzen wir diese Zeilen mit einer Anzahl an Protokollen von 0 und einer Compliance von 0%
#Berechnung der Compliance pro Person über alle Studientage hinweg
compliance_person_level <- compliance |>
group_by(id) |>
summarize(anz_protokolle = sum(n),
compliance_p = mean(compliance)) |>
ungroup()
#Durchschnittliche Anzahl ausgefüllter Protokolle
compliance_person_level |>
select(anz_protokolle) |>
map_dfr(~ tibble
( Personen = length(.x),
M_Protokolle = mean(.x, na.rm = TRUE),
SD_Protokolle = sd(.x, na.rm = TRUE),
Min_Protokolle = min(.x, na.rm = TRUE),
Max_Protokolle = max(.x, na.rm = TRUE)),
.id = "variable")#Durchschittliche Compliance
compliance_person_level |>
select(compliance_p) |>
map_dfr(~ tibble
(M = mean(.x, na.rm = TRUE),
SD = sd(.x, na.rm = TRUE),
Min = min(.x, na.rm = TRUE),
Max = max(.x, na.rm = TRUE)),
.id = "variable")Wir sehen, dass die Teilnehmer:innen, die unsere Bedingungen erfüllt haben, mindestens 4 und maximal 47 (von möglichen 48) Protokollen ausgefüllt haben (M = 28,7, SD = 11,1). Die Compliance liegt im Schnitt bei 59,7% (SD = 23,0, Min = 8,3, Max = 97,9).
Die durchschnittliche Response Latency können wir auf Basis unserer Daten leider nicht auswerten, weil keine Informationen über die Alarmierungszeitpunkte vorliegen. Die Vorgehensweise entspricht aber der bei der durchschnittlichen Ausfülldauer (siehe oben).
B.3 Schritt 3 – Prüfung von möglichen systematischen Verzerrungen und Umgang damit
B.3.1 Einzelne Situationen
Zunächst verschaffen wir uns einen Eindruck davon, ob es besonders schnell beantwortete Protokolle gibt.
data_situation |>
select(ausfuelldauer) |>
arrange(ausfuelldauer)Es gibt etliche Protokolle, die in wenigen Sekunden ausgefüllt wurden. Dies könnte daran liegen, dass Protokolle ohne Podcast-Nutzung nur sehr wenige Fragen enthalten.
data_situation |>
select(ausfuelldauer, pod_nutz) |>
arrange(ausfuelldauer)Wir sehen, dass fast alle sehr kurzen Protokolle aus Situationen stammen, in denen die Teilnehmer:innen keinen Podcast gehört haben. Das erscheint plausibel und unproblematisch, da sie hier auch nur wenige kurze Fragen beantworten mussten.
Nun schauen wir uns gezielt nur die Messzeitpunkte an, in denen die Teilnehmer:innen die meisten Fragen beantworten mussten, das sind die Fälle mit True-Crime-Nutzung.
data_situation |>
filter(pod_nutz == "Ja, True-Crime-Podcast") |>
select(ausfuelldauer, pod_nutz) |>
arrange(ausfuelldauer)Hier gibt es auch einige Protokolle, die sehr schnell beantwortet wurden. Dies behalten wir für die spätere Datenbereinigung im Hinterkopf.
Die Response Latency für die einzelnen Protokolle können wir auf Basis unserer Daten leider wieder nicht auswerten. Die Vorgehensweise entspricht aber der bei der durchschnittlichen Ausfülldauer (siehe oben).
B.3.2 Personenunterschiede
Zunächst schauen wir uns an, ob sich die Personen, die die ESM-Studie erfolgreich abgeschlossen haben, von den Abbrecher:innen unterscheiden (siehe Abschnitt B.2).
#hierfür erstellen wir zunächst eine Variable, die enthält, ob eine Person unsere obigen Bedingungen erfüllt hat
attrition_post <- attrition_post |>
mutate(abgeschlossen = if_else(anz_esm_3 == "mind. 3 Protokolle" & tn_postsurvey == "ja", "ja", "nein"))
#dann suchen wir nach Gruppenunterschieden, hier bzgl. Geschlecht und Alter
table <- attrition_post |>
select(soz_gender, abgeschlossen) |>
table()
table <- table |>
addmargins(margin = 2) |>
prop.table(margin = 2)
round(table * 100, digits = 0) abgeschlossen
soz_gender ja nein Sum
divers 2 2 2
maennlich 14 11 12
weiblich 84 87 86table |>
chisq.test()
Pearson's Chi-squared test
data: table
X-squared = 0.005, df = 4, p-value = 1attrition_post |>
group_by(abgeschlossen) |>
summarise(
MW_Alter = mean(soz_age, na.rm = TRUE),
SD_Alter = sd(soz_age, na.rm = TRUE)
)t.test(soz_age ~ abgeschlossen, data = attrition_post) |>
report_table()Wir sehen keine signifikanten Unterschiede zwischen den Personen, die erfolgreich teilgenommen haben, und den Abbrecher:innen bzgl. Geschlecht und Alter.
Hinweis: Selbstverständlich kann es sinnvoll sein, auch andere Variablen aus der Vorab- oder Nachbefragung, die direkt mit dem Thema der Studie zu tun haben, zum Vergleich heranzuziehen.
Nun schauen wir uns an, ob es Personen gibt, die über alle Messzeitpunkte hinweg besonders schnell geantwortet haben.
data_situation |>
group_by(id) |>
summarise(Mittelwert = mean(ausfuelldauer, na.rm = TRUE),
Standardabweichung = sd(ausfuelldauer, na.rm = TRUE),
Minimum = min(ausfuelldauer, na.rm = TRUE),
Maximum = max(ausfuelldauer, na.rm = TRUE)
) |>
arrange(Mittelwert)Das ist der Fall: Wir haben einige Teilnehmer:innen, die insgesamt sehr kurz für das Ausfüllen der Protokolle gebraucht haben. Dies kann aber auch daran liegen, dass sie oft angegeben haben, keinen Podcast zu hören, denn dann war das Protokoll besonders kurz und somit wäre auch eine kurze Antwortzeit plausibel.
Das schauen wir uns im nächsten Schritt an, indem wir uns zusätzlich ausgeben lassen, wie hoch der Anteil an Situationen war, in denen kein Podcast genutzt wurde.
data_situation |>
group_by(id) |>
summarise(Mittelwert = mean(ausfuelldauer, na.rm = TRUE),
Standardabweichung = sd(ausfuelldauer, na.rm = TRUE),
Minimum = min(ausfuelldauer, na.rm = TRUE),
Maximum = max(ausfuelldauer, na.rm = TRUE),
Anzahl_Messzeitpunkte = n(),
Anteil_kein_Podcast = (sum(pod_nutz == "Nein, kein Podcast"))/n()) |>
arrange(Mittelwert)Wir sehen, dass diejenigen, bei denen die Ausfülldauer besonders kurz war, auch selten angegaben, einen Podcast gehört zu haben. Damit spricht die kurze Antwortzeit nicht unbedingt für Careless Responding.
Die durchschnittliche Response Latency pro Person können wir auf Basis unserer Daten leider nicht auswerten. Die Vorgehensweise entspricht aber der bei der durchschnittlichen Ausfülldauer (siehe oben).
Nun schauen wir uns an, ob es Personen mit besonders niedriger Compliance gibt. Hierfür betrachten wir nur diejenigen, die laut unserer Kriterien (siehe unsere festgelegten Bedingungen für den erfolgreichen Studienabschluss in Abschnitt B.2) die Studie erfolgreich abgeschlossen haben.
compliance_person_level |>
arrange(compliance_p)Wir sehen, dass wir Personen mit einer sehr niedrigen Compliance im Datensatz haben (das ist logisch, da wir Teilnehmer:innen ab drei von möglichen 48 Protokollen in der Stichprobe haben). Alternativ können wir auch eine Mindest-Compliance festlegen, ab der Teilnehmer:innen in die finale Stichprobe aufgenommen werden (siehe Abschnitt 9.2.1).
Wir können uns auch anschauen, ob es Personen mit bestimmten Merkmalen sind, die eine niedrige Compliance aufweisen.
#hierfür spielen wir zunächst die Compliance an den Personendatensatz an
compliance_check <- left_join(attrition_post, compliance_person_level, by="id") |>
filter(anz_esm_3 == "mind. 3 Protokolle" & tn_postsurvey == "ja")
#dann suchen wir nach Personenunterschieden, hier bzgl. Geschlecht und Alter
ttest <- compliance_check |>
mutate(soz_gender_d = ifelse(soz_gender == "weiblich", "weiblich", "maennlich/divers"))
ttest |>
group_by(soz_gender_d) |>
summarise(
MW_Compliance = mean(compliance_p, na.rm = TRUE),
SD_Compliance = sd(compliance_p, na.rm = TRUE))t.test(compliance_p ~ soz_gender_d, data = ttest) |>
report_table()cor.test(~ soz_age + compliance_p, data = compliance_check) |>
report_table()Wir sehen, dass das Geschlecht keinen Zusammenhang mit der Compliance hat (t(19,67) = -0,65, p = ,523), aber das Alter: Ältere Teilnehmer:innen weisen eine höhere Compliance auf (r = ,27, p = ,006).
B.3.3 Zeitverläufe
Nun wollen wir schauen, ob es in der Verteilung der Variablen zeitliche Muster gibt.
Wir beginnen mit zeitlichen Mustern in der Compliance.
compliance_day_level <- compliance |>
group_by(studientag) |>
summarise(Mittelwert = mean(compliance, na.rm = TRUE),
Standardabweichung = sd(compliance, na.rm = TRUE),
Minimum = min(compliance, na.rm = TRUE),
Maximum = max(compliance, na.rm = TRUE))
ggplot(compliance_day_level, aes(x = studientag, y = Mittelwert)) +
geom_line() +
labs(x = "Studientag", y = "Compliance", title = "Compliance im Studienverlauf") +
theme_bw() +
scale_x_continuous(breaks = seq(1, 12, 1), limits = c(1, 12)) +
scale_y_continuous(limits = c(0, 100))
Wir beobachten keine extremen Ausreißer und lediglich einen erwartbaren moderaten Abfall über die Feldzeit hinweg. Lediglich der erste Studientag fällt mit einer niedrigen Compliance auf, die allerdings nicht deutlich unter dem Gesamtniveau liegt.
Auch dies können wir uns nun für die Teilnehmer:innen getrennt anschauen. Um es übersichtlich zu gestalten, können wir uns die Verläufe in Paketen von je 20 Teilnehmer:innen anzeigen lassen.
compliance_g <- compliance |>
mutate(id_num = as.numeric(as.factor(id)))
compliance_1 <- compliance_g |>
filter(id_num %in% unique(compliance_g$id_num)[1:20])
ggplot(data = compliance_1, aes(x = studientag, y = compliance )) +
geom_line() +
facet_wrap(~ id, ncol = 5)+
scale_x_continuous(breaks = seq(1, 12, 1), limits = c(1, 12)) +
scale_y_continuous(limits = c(0, 100), breaks = seq(20, 100, 20)) +
theme_minimal()
compliance_1 <- compliance_g |>
filter(id_num %in% unique(compliance_g$id_num)[21:40])
ggplot(data = compliance_1, aes(x = studientag, y = compliance )) +
geom_line() +
facet_wrap(~ id, ncol = 5)+
scale_x_continuous(breaks = seq(1, 12, 1), limits = c(1, 12)) +
scale_y_continuous(limits = c(0, 100), breaks = seq(20, 100, 20)) +
theme_minimal()
compliance_1 <- compliance_g |>
filter(id_num %in% unique(compliance_g$id_num)[41:60])
ggplot(data = compliance_1, aes(x = studientag, y = compliance )) +
geom_line() +
facet_wrap(~ id, ncol = 5)+
scale_x_continuous(breaks = seq(1, 12, 1), limits = c(1, 12)) +
scale_y_continuous(limits = c(0, 100), breaks = seq(20, 100, 20)) +
theme_minimal()
Wir sehen, dass es einige Teilnehmer:innen gibt, bei denen die Compliance über die Feldzeit abfällt (z. B. 5M4E64V4UR in der ersten Abbildung).
Zusätzlich können wir uns z. B. noch anschauen, ob es größere Unterschiede in der Compliance zwischen den Wochentagen und den Wochenendtagen gibt.
compliance_day_level <- compliance_day_level |>
mutate(wt_we = ifelse(studientag %in% c(6, 7, 11, 12), "Wochenende", "Wochentag"))
compliance_day_level |>
group_by(wt_we) |>
summarise(Compliance = mean(Mittelwert, na.rm = TRUE)) Wir sehen, dass die Compliance an den Wochentagen etwas höher liegt als am Wochenende.
Die Response Latency können wir auf Basis unserer Daten leider nicht auswerten. Die Vorgehensweise entspricht aber der bei der Compliance.
Weiterhin könnten wir z. B. annehmen, dass die Teilnehmer:innen im Laufe der Feldzeit seltener angeben, einen Podcast gehört zu haben, um weniger Fragen zu beantworten zu müssen.
Dies schauen wir uns zunächst über alle Teilnehmer:innen hinweg an.
anz_nutzung_ges <- as_tibble(data_situation_ab |>
group_by(studientag) |>
summarise(count = sum(ifelse(pod_nutz == "Nein, kein Podcast", 0, 1)), .groups = "drop"))
ggplot(data = anz_nutzung_ges, aes(x = studientag, y = count)) +
geom_line() +
scale_x_continuous(breaks = seq(1, 12, 1), limits = c(1, 12)) +
scale_y_continuous(limits = c(0, 500)) +
theme_minimal()
Wir sehen einen deutlichen Abfall der berichteten Podcastnutzungsepisoden.
Interessant ist es, sich dies auf Personenebene anzuschauen, denn dort erkennen wir, ob einzelne Personen über die Feldzeit hinweg das angenommene Muster zeigen.
#Um die Ansicht übersichtlich zu machen, werden die Verläufe in Paketen von je 20 Teilnehmer:innen angezeigt.
data_situation_ab <- data_situation_ab |>
mutate(id_num = as.numeric(as.factor(id))) #hierfür vergeben wir zunächst eine numerische ID, da unsere ID eine Zeichenfolge ist.
anz_nutzung <- as_tibble(data_situation_ab |>
filter(id_num %in% unique(data_situation_ab$id_num)[1:20]) |> # dann können wir hier die ersten 20 Fälle auswählen
group_by(studientag, id) |>
summarise(count = sum(ifelse(pod_nutz == "Nein, kein Podcast", 0, 1)), .groups = "drop"))
ggplot(data = anz_nutzung, aes(x = studientag, y = count)) +
geom_line() +
facet_wrap(~ id, ncol = 5) +
scale_x_continuous(breaks = seq(1, 12, 1), limits = c(1, 12)) +
scale_y_continuous(limits = c(0, 4)) +
theme_minimal()
anz_nutzung <- as_tibble(data_situation_ab |>
filter(id_num %in% unique(data_situation_ab$id_num)[21:40]) |> # ...hier die nächsten 20 Fälle
group_by(studientag, id) |>
summarise(count = sum(ifelse(pod_nutz == "Nein, kein Podcast", 0, 1)), .groups = "drop"))
ggplot(data = anz_nutzung, aes(x = studientag, y = count)) +
geom_line() +
facet_wrap(~ id, ncol = 5) +
scale_x_continuous(breaks = seq(1, 12, 1), limits = c(1, 12)) +
scale_y_continuous(limits = c(0, 4)) +
theme_minimal()
anz_nutzung <- as_tibble(data_situation_ab |>
filter(id_num %in% unique(data_situation_ab$id_num)[41:60]) |> # ...usw.
group_by(studientag, id) |>
summarise(count = sum(ifelse(pod_nutz == "Nein, kein Podcast", 0, 1)), .groups = "drop"))
ggplot(data = anz_nutzung, aes(x = studientag, y = count)) +
geom_line() +
facet_wrap(~ id, ncol = 5) +
scale_x_continuous(breaks = seq(1, 12, 1), limits = c(1, 12)) +
scale_y_continuous(limits = c(0, 4)) +
theme_minimal()
Bei einzelnen Teilnehmer:innen finden wir solch ein Muster (z. B. bei 6H53PLCFAB in der ersten Abbildung).
Weiterin können wir prüfen, ob sich Response Shifts bei Skalenfragen zeigen, hier am Beispiel der Stimmung.
#um die Ansicht übersichtlich zu machen, werden die Verläufe in Paketen von je 20 Teilnehmer:innen angezeigt.
stimmung_zeitverlauf <- data_situation_ab |>
filter(id_num %in% unique(data_situation_ab$id_num)[1:20])
ggplot(data = stimmung_zeitverlauf, aes(x = protokoll_ges, y = stimmung )) +
geom_line() +
facet_wrap(~ id, ncol = 5)+
scale_x_continuous(breaks = seq(5, 48, 5), limits = c(1, 48)) +
scale_y_continuous(limits = c(1, 5), breaks = seq(1, 5, 1)) +
theme_minimal()
stimmung_zeitverlauf <- data_situation_ab |>
filter(id_num %in% unique(data_situation_ab$id_num)[21:40])
ggplot(data = stimmung_zeitverlauf, aes(x = protokoll_ges, y = stimmung )) +
geom_line() +
facet_wrap(~ id, ncol = 5)+
scale_x_continuous(breaks = seq(5, 48, 5), limits = c(1, 48)) +
scale_y_continuous(limits = c(1, 5), breaks = seq(1, 5, 1)) +
theme_minimal()
stimmung_zeitverlauf <- data_situation_ab |>
filter(id_num %in% unique(data_situation_ab$id_num)[41:60])
ggplot(data = stimmung_zeitverlauf, aes(x = protokoll_ges, y = stimmung )) +
geom_line() +
facet_wrap(~ id, ncol = 5)+
scale_x_continuous(breaks = seq(5, 48, 5), limits = c(1, 48)) +
scale_y_continuous(limits = c(1, 5), breaks = seq(1, 5, 1)) +
theme_minimal()
Response Shifts zeigen sich hier nicht erkennbar: Wir sehen keine systematische Anpassung der Antworten nach den ersten Tagen.
Schließlich können wir auch noch prüfen, ob die Antworten der Teilnehmer:innen im Zeitverlauf varianzärmer werden. Auch hier nehmen wir die Stimmung als Beispiel.
#um die Ansicht übersichtlich zu machen, werden die Verläufe in Paketen von je 20 Teilnehmenden angezeigt.
varianz <- as_tibble(data_situation_ab |>
filter(id_num %in% unique(data_situation_ab$id_num)[1:20]) |>
group_by(studientag, id) |>
summarise(VAR = var(stimmung, na.rm = TRUE), .groups = "drop"))
ggplot(data = varianz, aes(x = studientag, y = VAR)) +
geom_line() +
facet_wrap(~ id, ncol = 5) +
scale_x_continuous(breaks = seq(1, 12, 1), limits = c(1, 12)) +
theme_minimal()
varianz <- as_tibble(data_situation_ab |>
filter(id_num %in% unique(data_situation_ab$id_num)[21:40]) |>
group_by(studientag, id) |>
summarise(VAR = var(stimmung, na.rm = TRUE), .groups = "drop"))
ggplot(data = varianz, aes(x = studientag, y = VAR)) +
geom_line() +
facet_wrap(~ id, ncol = 5) +
scale_x_continuous(breaks = seq(1, 12, 1), limits = c(1, 12)) +
theme_minimal()
varianz <- as_tibble(data_situation_ab |>
filter(id_num %in% unique(data_situation_ab$id_num)[41:60]) |>
group_by(studientag, id) |>
summarise(VAR = var(stimmung, na.rm = TRUE), .groups = "drop"))
ggplot(data = varianz, aes(x = studientag, y = VAR)) +
geom_line() +
facet_wrap(~ id, ncol = 5) +
scale_x_continuous(breaks = seq(1, 12, 1), limits = c(1, 12)) +
theme_minimal()
Es ist kein Trend zu varianzärmeren Antworten erkennbar: Wir sehen bei keinem Befragen ein abfallendes Muster (in der Tendenz am ehesten bei 3L289ZY38E in der ersten Abbildung).
B.4 Datenbereinigung
Haben wir uns auf Basis der vorherigen Checks entschieden, die Daten zu bereinigen, löschen wir die Fälle am besten nicht aus dem Datensatz, sondern erstellen Variablen, die uns das Filtern der Fälle für die weiteren Analysen ermöglichen.
Wir entscheiden uns auf Basis der obigen Checks dagegen, Teilnehmer:innen oder Protokolle für die folgenden Analysen auszuschließen.
Wichtig: Die Überlegungen, unter welchen Bedingungen man Personen oder Protokolle ausschließt, sollten im Vorfeld getroffen und idealerweise präregistriert werden, siehe Abschnitt 10.2.
Beispielhaft zeigen wir aber, wie man Protokolle markieren würde, die eine Antwortzeit von unter 20 Sekunden haben, obwohl die Teilnehmer:innen über ihre Podcast-Nutzung berichten (bei denen, die keinen Podcast nutzen, legen wir keine Mindestdauer fest), um diese ggf. bei den folgenden Analysen auszuschließen.
data_situation_ab <- data_situation_ab |>
mutate(filter_auswertung = if_else(pod_nutz == "Nein, kein Podcast" | (ausfuelldauer >= 20 & pod_nutz != "Nein, kein Podcast"), "gültig", "ungültig"))Dann speichern wir den bereinigten Datensatz ab.
openxlsx::write.xlsx(data_situation_ab, "data/data_situation_bereinigt.xlsx")