Repository navigation
Expand file tree
/
Copy path02-Data-processing.Rmd
More file actions
1555 lines (1405 loc) · 50.8 KB
/
Copy path02-Data-processing.Rmd
File metadata and controls
1555 lines (1405 loc) · 50.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Data processing {#data-processing}
Let's process the raw data and get them to a format that we can work with for the analysis.
The study has three raw data files:
* `personality_raw.csv` is the survey participants filled out at the beginning of the study, reporting personality traits
* `diary_raw.csv` is the end of day survey participants filled out during the diary part of the study
* `screen_time_raw.csv` is the objective screen time data participants reported
## Personality data
The data are on the OSF.
You can download them if you set the code chunk below to `eval=TRUE`.
Downloading only works when the OSF project is public.
If it isn't during peer review, you'll need to paste the data files from the OSF to the `data/` folder manually.
```{r download-data, eval=TRUE, message=FALSE}
# create directory
dir.create("data/", FALSE, TRUE)
# download models
osf_retrieve_node("https://osf.io/7byvt/") %>%
osf_ls_nodes() %>%
filter(name == "data") %>%
osf_ls_files(
.,
n_max = Inf
) %>%
osf_download(
.,
path = here("data"),
progress = FALSE
)
```
Let's load the personality data.
```{r load-personality-data, message=FALSE}
personality_raw <- read_csv(here("data", "personality_raw.csv"))
```
Qualtrics names aren't very informative, so I give some sensible variable names (although this data set has mostly meaningful variable names).
Also, there are variables (i.e., anything starting with `afi` to `selfreflect`) that were part of a different study, which is why I don't include them here.
Note that I don't want to change the raw data object, so I'll keep using `personality` as a new object from here on.
```{r rename-personality-data}
personality <-
personality_raw %>%
select(
# meta data
start_date = StartDate,
end_date = EndDate,
progress = Progress,
duration = Duration__in_seconds_,
finished = Finished,
recorded_date = RecordedDate,
id = study_id,
date,
# basic psychological need satisfaction trait
bpns_ = starts_with("bpns"),
# big five
big_five_ = starts_with("bfi"),
# demographics
gender,
gender_entry = gender_3_TEXT,
age = Q42,
ethnicity_american_indian = ethnicity_1,
ethnicity_asian = ethnicity_2,
ethnicity_island = ethnicity_3,
ethnicity_black = ethnicity_4,
ethnicity_white = ethnicity_5,
ethnicity_hispanic = ethnicity_6,
ethnicity_multi = ethnicity_7
)
```
Next, I make sure those variables have the correct variable type.
I'll also recode factor levels so that labels are readable.
Note that participants filled in their age from a dropdown menu to prevent typos.
The dropdown went from age 18 (`1`) to 30+ (`13`).
Nobody was older than thirty, so I'll recode age as well.
```{r recode-personality-data}
personality <-
personality %>%
mutate(
id = paste0("pp_", id), # add pp for participant just so later I don't mistake the id variable for a count
across( # turn those two into factors
c(finished, id, gender),
as.factor
),
finished = fct_recode(
finished,
"yes" = "1",
"no" = "0"
),
gender = fct_recode(
gender,
"male" = "1",
"female" = "2",
"other" = "3"
),
age = age + 17
)
```
I see that ethnicity was multiple choice although "multiracial" is an answer option.
So I think it makes more sense to combine this all into one variable.
That required quite a lot of code, but was still less problematic than transforming the ethnicity variables to long format.
I took the following steps:
* check who reported multiple ethnicities
* those who did get an `NA` on all other ethnicity variables
* recoded the variables from numeric to containing the ethnicity factor level and coalesce
```{r recode-ethnicity-personality}
personality <-
personality %>%
mutate(
multiple = rowSums(select(., starts_with("ethnicity")), na.rm = TRUE), # check who reported multiple ethnicities
# those with multiple get NA for all other ethnicity variables
across(
c(ethnicity_american_indian:ethnicity_hispanic),
~ case_when(
multiple > 1 ~ NA_real_,
TRUE ~ .
)
),
# turn into character which makes recoding below easier
across(
c(ethnicity_american_indian:ethnicity_hispanic),
as.character
),
# recode the individual variables
ethnicity_american_indian = if_else(ethnicity_american_indian == 1, "American Indian/Alaska Native", NA_character_),
ethnicity_asian = if_else(ethnicity_asian == 1, "Asian", NA_character_),
ethnicity_island = if_else(ethnicity_island == 1, "Native Hawaiian or Other Pacific Islander", NA_character_),
ethnicity_black = if_else(ethnicity_black == 1, "Black or African American", NA_character_),
ethnicity_white = if_else(ethnicity_white == 1, "White", NA_character_),
ethnicity_hispanic = if_else(ethnicity_hispanic == 1, "Hispanic or Latino", NA_character_),
ethnicity_multi = if_else(multiple > 1, "Multiracial", NA_character_),
# coalesce them (coalesce doesn't work with .select helpers, so needed to type the variables out)
ethnicity = as.factor(
coalesce(
ethnicity_american_indian,
ethnicity_asian,
ethnicity_island,
ethnicity_black,
ethnicity_white,
ethnicity_hispanic,
ethnicity_multi
)
)
) %>%
select(start_date:age, ethnicity)
```
Then I remove those cases without an `id` (aka test runs) and those with an `id` but not other entries.
The latter are probably no-shows.
Note: In the earlier versions of this script, I excluded per data set, which made it hard for reporting exact exclusions in the final paper.
For example, at this point I don't know whether someone I exclude here even participated in the diary part of the study.
Therefore, I save each exclusion criterion in a separate object and inspect how many people we actually need to exclude later in the data merging section.
```{r exclude-test-runs-personality}
# remove test run
personality <-
personality %>%
filter(id != "pp_NA")
personality_exclusions_1 <-
personality %>%
filter(is.na(start_date)) %>%
pull(id)
```
Let's see how many missing values there are left per participant (aka row) in \@ref(tab:check-personality-na).
Most participants filled out everything, but a couple of them have basically empty rows (especially those above with all empty rows).
A couple of participants have quite a handful of empty cells.
I inspected their data and most of them just didn't fill out the Big Five, but some participants didn't fill out anything.
```{r check-personality-na, echo=FALSE}
# check nas per row
personality %>%
mutate(
na_count_row = rowSums(is.na(.))
) %>%
count(na_count_row) %>%
knitr::kable(
.,
caption = "Count of number of missing values per row"
)
```
I'll exclude the latter.
```{r}
# exclude those with > 15 missing values (which identifies empty rows)
personality_exclusions_2 <-
personality %>%
mutate(
na_count_row = rowSums(is.na(.))
) %>%
filter(na_count_row >70) %>%
pull(id)
```
Alright, time to construct the means for the scales.
But first, I check data quality by seeing whether there are instances of "straightlining" (i.e., choosing the same response option for all items of a scale).
We also had attention checks in the survey.
Let's have a look at those attention checks.
For item `bpns_25` participants had to choose `2` as a response and for item `big_five_45` they had to choose `5`.
When we check Table \@ref(tab:attention-check-personality), we see that `r personality %>% filter((bpns_25 != 2 & big_five_45 != 5)) %>% nrow()` participants didn't pass both attention checks.
```{r attention-check-personality, echo=FALSE}
knitr::kable(
list(
personality %>%
count(bpns_25 != 2),
personality %>%
count(big_five_45 != 5),
personality %>%
count((bpns_25 != 2 & big_five_45 != 5))
),
caption = "Failed attention checks"
)
```
I'll exclude those participants because I don't think we can trust their data.
```{r exclude-failed-attention-personality}
personality_exclusions_3 <-
personality %>%
filter(bpns_25 != 2 & big_five_45 != 5) %>%
pull(id)
```
Next, I check for straightlining by inspecting the variance per daily survey on the BPNS and BF scales.
Turns out that it's not super straightforward to compute variance per row, found help [here](https://www.tidyverse.org/blog/2020/04/dplyr-1-0-0-rowwise/).
```{r straightlining-personality}
personality <-
personality %>%
rowwise() %>%
mutate(
bpns_sd = sd(
c_across(starts_with("bpns")),
na.rm = TRUE
),
big_five_sd = sd(
c_across(starts_with("big_five")),
na.rm = TRUE
)
) %>%
unnest(cols = c())
```
If we visualize that variance in Figure \@ref(fig:plot-variance-personality), we can see that there's little cause for concern: few people straightlined.
```{r plot-variance-personality, echo = FALSE, warning=FALSE, message=FALSE, fig.cap="Distribution of variances per survey on two measures"}
plot_grid( # from cowplot
dens_with_points(personality, "bpns_sd"), # custom function, see custom functions under setting up
dens_with_points(personality, "big_five_sd"),
labels = c("BPNS", "Big Five"),
ncol = 1,
label_size = 8,
hjust = 0,
vjust = 0,
label_x = 0,
label_y = 0.93
)
```
Those two are already captured by the exclusion criteria above (I checked manually, but I'll still save them in an object).
```{r personality-straightliners}
personality_exclusions_4 <-
personality %>%
filter(bpns_sd == 0 | big_five_sd == 0) %>%
pull(id)
```
Alright, now we can aggregate the individual items to scale scores.
Some of those items are reverse-coded (see the codebook for details).
I'll code all scales such that a higher scores means scoring more positively on that scale.
```{r reverse-code-personality}
personality <-
personality %>%
mutate(
across(
c(
bpns_5:bpns_8,
bpns_13:bpns_15,
bpns_21:bpns_25,
),
function(.) 8 - .
),
across(
c(
big_five_2,
big_five_6,
big_five_8,
big_five_9,
big_five_12,
big_five_18,
big_five_21,
big_five_23,
big_five_27,
big_five_31,
big_five_37,
big_five_43
),
function(.) 6 - .
)
)
```
Now I create the scales by taking the mean of the respective items.
Note that I remove missing values in computing row means to ignore the occasional missing value.
(For those with missing values throughout, see next code chunk.)
```{r make-scales-personality}
personality <-
personality %>%
mutate(
# trait needs
autonomy_trait = rowMeans(select(., bpns_1:bpns_8), na.rm = TRUE),
competence_trait = rowMeans(select(., bpns_9:bpns_16), na.rm = TRUE),
relatedness_trait = rowMeans(select(., bpns_17:bpns_24), na.rm = TRUE),
# big five
extraversion = rowMeans(
select(
.,
big_five_1,
big_five_6,
big_five_11,
big_five_16,
big_five_21,
big_five_26,
big_five_31,
big_five_36
),
na.rm = TRUE
),
agreeableness = rowMeans(
select(
.,
big_five_2,
big_five_7,
big_five_12,
big_five_17,
big_five_22,
big_five_27,
big_five_32,
big_five_37,
big_five_42
),
na.rm = TRUE
),
conscientiousness = rowMeans(
select(
.,
big_five_3,
big_five_8,
big_five_13,
big_five_18,
big_five_23,
big_five_28,
big_five_33,
big_five_38,
big_five_43
),
na.rm = TRUE
),
neuroticism = rowMeans(
select(
.,
big_five_4,
big_five_9,
big_five_14,
big_five_19,
big_five_24,
big_five_29,
big_five_34,
big_five_39
),
na.rm = TRUE
),
openness = rowMeans(
select(
.,
big_five_5,
big_five_10,
big_five_15,
big_five_20,
big_five_25,
big_five_30,
big_five_35,
big_five_40,
big_five_41,
big_five_44
),
na.rm = TRUE
)
)
```
Last, some participants didn't fill out the Big Five items, so they haven an `NaN` entry for those scales now.
I'll turn that into an `NA` value
```{r nan-to-na-personality}
personality <-
personality %>%
mutate(
across(
c(autonomy_trait:openness),
~ na_if(.x, "NaN")
)
)
```
## Diary data
Let's load and inspect the diary data.
```{r load-diary-data, message=FALSE}
# load data
diary_raw <- read_csv(here("data", "diary_raw.csv"))
```
Qualtrics names aren't very informative, so I give some sensible variable names.
Again, there are many variables (i.e., anything before `Q21-1`) that were part of a different study, which is why I don't include them here.
Like before, I don't want to change the raw data object, so I'll keep using `diary` as a new object from here on.
```{r rename-diary-data}
diary <-
diary_raw %>%
select(
# meta data
start_date = StartDate,
end_date = EndDate,
progress = Progress,
duration = Duration__in_seconds_,
finished = Finished,
recorded_date = RecordedDate,
id = study_id,
day = Day,
date = Q27,
# social media subjective estimates
hours_subjective = Q21_1,
minutes_subjective = Q21_2,
pickups_subjective = Q22_1,
notifications_subjective = Q23_1,
# arousal
low_positive_peaceful = Q24_1,
low_positive_calm = Q24_2,
low_positive_related = Q24_3,
high_negative_anxious = Q24_4,
high_negative_jittery = Q24_5,
high_negative_tense = Q24_6,
high_positive_happy = Q24_7,
high_positive_energized = Q24_8,
high_positive_excited = Q24_9,
low_negative_sluggish = Q24_10,
low_negative_sad = Q24_11,
low_negative_gloomy = Q24_12,
# autonomy need satisfaction
autonomy_ = Q25_1:Q25_4,
# competence need satisfaction
competence_ = Q25_5:Q25_8,
# relatedness need satisfaction
relatedness_ = Q25_9:Q25_12,
# experiences
satisfied = Q30_1,
boring = Q30_2,
stressful = Q30_3,
enjoyable = Q30_4
)
```
Next, I make sure those variables have the correct variable type.
I'll also recode factor levels so that labels are readable.
Note that `day` goes from `1` to `5`.
All participants started on a Monday, which is why I turn `day` into a factor and reorder the levels.
```{r recode-diary-data}
diary <-
diary %>%
mutate(
id = paste0("pp_", id), # add pp for participant just so later I don't mistake the id variable for a count
across( # turn those two into factors
c(finished, id, day),
as.factor
),
finished = fct_recode(
finished,
"yes" = "1",
"no" = "0"
),
day = fct_recode(
day,
"monday" = "1",
"tuesday" = "2",
"wednesday" = "3",
"thursday" = "4",
"friday" = "5"
)
)
```
Then I identify all empty rows (i.e., rows without a start date, so people who have placeholder `id`, but didn't participate on that day).
```{r check-diary-na}
diary_exclusions_1 <-
diary %>%
filter(is.na(start_date)) %>%
select(id, day)
```
Afterwards, I inspect how many `NA`s there are per row in Table \@ref(tab:table-diary-na).
Most participants filled out everything, but a couple of them have basically empty rows (e.g., those with 30 or 40 missing values per row).
```{r table-diary-na, echo=FALSE}
# check nas per row
diary %>%
mutate(
na_count_row = rowSums(is.na(.))
) %>%
count(na_count_row) %>%
knitr::kable(
.,
caption = "Count of number of missing values per row"
)
```
I manually inspected those with more than 15 missing values and neither of them even finished any of the scales.
Therefore, I'll exclude them here.
```{r exclude-missings-diary, message=FALSE}
# exclude those with > 15 missing values
diary_exclusions_2 <-
diary %>%
mutate(
na_count_row = rowSums(is.na(.))
) %>%
filter(na_count_row > 15) %>%
mutate(id = droplevels(id)) %>%
select(id, day)
```
Alright, time to construct the means for the scales.
But first, as always with numbers that participants have to fill in themselves, I'll check for unrealistic entries and general data quality.
I'll visualize the text entries for hours and minutes on social media and notifications in Figure \@ref(fig:plot-subjective-diary).
```{r plot-subjective-diary, echo = FALSE, warning=FALSE, message=FALSE, fig.cap="Distribution of self-reported social media indicators"}
plot_grid( # from cowplot
dens_with_points(diary, "hours_subjective"), # custom function, see custom functions under setting up
dens_with_points(diary, "minutes_subjective"),
dens_with_points(diary, "notifications_subjective"),
labels = c("Hours", "Minutes", "Notifications"),
ncol = 1,
label_size = 8,
hjust = 0,
vjust = 0,
label_x = 0,
label_y = 0.93
)
```
For notifications, it's hard to argue that it's unrealistic that participants thought they got several hundred of them.
So I will leave those unchanged.
However, there are several outliers in `hours_subjective` and `minutes_subjective`.
I manually inspected those cases and it's hard to tell whether participants just mixed up hours and minutes or whether their entry is indeed not meaningful.
One participant filled in 50h and 500min.
Another 40h.
For those two, I'll set their subjective values to `NA`.
One participant filled in 5h and 86min.
The minutes entry might have been a typo, but it's hard to tell what the participant meant, so I'll set that to `NA` as well.
```{r replace-impossible-subjective}
diary <-
diary %>%
mutate(
hours_subjective = case_when(
hours_subjective > 24 | minutes_subjective > 60 ~ NA_real_,
TRUE ~ hours_subjective
),
minutes_subjective = if_else(
minutes_subjective > 60,
NA_real_,
minutes_subjective
)
)
```
Next, I check data quality further by seeing whether there are instances of "straightlining".
For that, I check the variance per daily survey on the well-being scale, need satisfaction, and experiences.
```{r check-straightlining-diary}
diary <-
diary %>%
rowwise() %>%
mutate(
well_being_sd = sd(
c_across(low_positive_peaceful:low_negative_gloomy),
na.rm = TRUE
),
needs_sd = sd(
c_across(autonomy_1:relatedness_4),
na.rm = TRUE
),
experiences_sd = sd(
c_across(satisfied:enjoyable),
na.rm = TRUE
)
) %>%
unnest(cols = c())
```
If we visualize the instances where participants didn't have any variation in their answers (aka straightlining) in Figure \@ref(fig:plot-variance-diary), we see that it's not uncommon.
There are several daily surveys where participants straightlined.
```{r plot-variance-diary, echo = FALSE, warning=FALSE, message=FALSE, fig.cap="Distribution of variances per survey on three scales"}
plot_grid( # from cowplot
dens_with_points(diary, "well_being_sd"), # custom function, see custom functions under setting up
dens_with_points(diary, "needs_sd"),
dens_with_points(diary, "experiences_sd"),
labels = c("Well-being", "Need satisfaction", "Experiences"),
ncol = 1,
label_size = 8,
hjust = 0,
vjust = 0,
label_x = 0,
label_y = 0.93
)
```
In general, it's not uncommon to select the same answer, especially for well-being measures.
However, I'd be suspicious of those participants who straightlined for more than one scale (particularly the need satisfaction scale because it has quite a lot of items.)
I'll check how many surveys there are that have zero variance across those scales and how many participants there are who have at least one straightlining instance in more than 50% of their surveys.
```{r check-straightliners-diary, message=FALSE}
diary <-
diary %>%
mutate(
zero_variances = rowSums(
select(., ends_with("_sd")) == 0, # count instances of zero variance across the three scales
na.rm = TRUE
)
)
```
Overall, Table \@ref(tab:table-straightliners-diary) shows that 95% of the sample didn't staightline.
```{r table-straightliners-diary, echo=FALSE, message=FALSE}
# percentage of zero variances instances
diary %>%
count(zero_variances) %>%
mutate(
percent = n / sum(n)
) %>%
knitr::kable(
.,
caption = "Percentage of the number of zero variance per row"
)
```
But there are a couple of participants who seemed to rush through the survey, see Table \@ref(tab:table-percentage-straightliners-diary).
Manually inspecting them indeed shows that it's hard to trust their data.
(Note that I temporarily remove the rows identified above because we only want to exclude those straightliners with valid data.)
```{r table-percentage-straightliners-diary, message=FALSE, echo=FALSE}
# percentage of participants
diary %>%
anti_join(., diary_exclusions_1) %>%
anti_join(., diary_exclusions_2) %>%
group_by(id) %>%
count(zero_variances) %>%
mutate(
zero_instance = if_else(zero_variances > 0, 1, 0) # recode such that it counts when there is at least one straightlining per survey
) %>%
summarise(
total_surveys = sum(n),
total_straightline_surveys = sum(zero_instance)
) %>%
mutate(
percentage = total_straightline_surveys / total_surveys
) %>%
arrange(desc(percentage)) %>%
head(n = 10) %>%
knitr::kable(
.,
caption = "Percentage of participants with a number of zero-variance scales"
)
```
I will exclude those participants and then surveys with more than one instance of straightlining.
```{r exclude-straightliners-diary}
diary_exclusions_3 <-
c("pp_172", "pp_121", "pp_164", "pp_169", "pp_103", "pp_168", "pp_290")
```
Now there's very little straightlining left (see Table \@ref(tab:check-variances-again-diary) and compare to Table \@ref(tab:table-straightliners-diary)).
```{r check-variances-again-diary, echo=FALSE, message=FALSE}
diary %>%
anti_join(., diary_exclusions_1) %>%
anti_join(., diary_exclusions_2) %>%
filter(!id %in% diary_exclusions_3) %>%
count(zero_variances) %>%
mutate(
percent = n / sum(n)
) %>%
knitr::kable(
.,
caption = "Percentage of the number of zero variance per row"
)
```
Alright, now we can aggregate the individual items to scale scores.
Some of those items are reverse-coded.
Namely, the third and fourth item for the need satisfaction subscales are reverse coded, as are all negative arousal items (if we want to create a score that shows higher well-being).
See codebook for details.
```{r reverse-code-diary}
# reverse code need items and arousal
diary <-
diary %>%
mutate(
across(
c(ends_with("_3"), ends_with("_4")),
function(.) 8 - .
),
across(
contains("negative"),
function(.) 6 - .
)
)
```
I create the scales by taking the mean of the respective items.
I also create a `social_media_subjective` measure by combining hours and minutes of estimated social media time.
```{r make-scales-diary}
diary <-
diary %>%
mutate(
social_media_subjective = hours_subjective * 60 + minutes_subjective,
well_being_state = rowMeans(select(., low_positive_peaceful:low_negative_gloomy), na.rm = TRUE),
autonomy_state = rowMeans(select(., contains("autonomy")), na.rm = TRUE),
competence_state = rowMeans(select(., contains("competence")), na.rm = TRUE),
relatedness_state = rowMeans(select(., contains("relatedness")), na.rm = TRUE)
)
```
Further, participants' first day of participation was always a Monday.
Specifically, three days in April 2019: the 15th, 22nd, 29th.
Let's check whether all of them indeed have their first day on one of those days.
Table \@ref(tab:check-first-day) shows all day one surveys which were not recorded on one of those three dates.
Indeed, everyone opened their first survey on one of the three start days.
Some participants opened/recorded their answers right after midnight, which is fine.
```{r check-first-day, echo=FALSE}
diary %>%
group_by(id) %>%
filter(!is.na(start_date)) %>%
filter(day == "monday") %>%
select(id, start_date, recorded_date, duration, day) %>%
filter(!day(recorded_date) %in% c(15,22,29)) %>%
knitr::kable(
.,
caption = "Recorded dates that weren't on a Monday"
)
```
However, a handful of participants had their survey open for at least a day.
In Table \@ref(tab:check-two-day-delay), we inspect all instances where there's more than one day delay between opening and responding to a survey.
Only `pp_71` took two days to respond to a survey.
```{r check-two-day-delay, echo=FALSE, message=FALSE}
diary %>%
anti_join(., diary_exclusions_1) %>%
anti_join(., diary_exclusions_2) %>%
filter(!id %in% diary_exclusions_3) %>%
select(id, start_date, recorded_date, duration, day) %>%
filter((day(recorded_date) - day(start_date)) > 1) %>%
knitr::kable(
.,
caption = "More than one day between begin and response to survey"
)
```
I guess it's normal that participants open the survey late, forget it, and fill it out when they next day when they wake up.
Because they're students, waking up might be quite late, so I'll check in Table \@ref(tab:next-day-late) how many of them responded to a survey later than 10am the next day.
Two participants were really late (i.e., responded after noon).
```{r next-day-late, echo=FALSE}
diary %>%
anti_join(., diary_exclusions_1) %>%
anti_join(., diary_exclusions_2) %>%
filter(!id %in% diary_exclusions_3) %>%
filter(day(recorded_date) != day(start_date)) %>%
filter(hour(recorded_date) > 9) %>%
select(id, start_date, recorded_date, duration, day) %>%
knitr::kable(
.,
caption = "Those who filled in the survey the next day"
)
```
I manually checked the data from all those participants who filled out surveys so late that two surveys were recorded on the same day.
`pp_71` responded to the Monday survey on Wednesday, in addition to the "regular" Wednesday survey.
For that reason, we can't know whether `day` one refers to Monday or Tuesday.
All other participants are fine: They sometimes recorded their response late, but their response pattern shows no indication that they couldn't refer to a clear day in their response.
Therefore, I remove the `day` one survey of `pp_71` because we can't tell to which day it refers, but leave the rest as is.
```{r remove-pp71}
diary_exclusions_4 <-
tibble(id = "pp_71", day = "monday")
```
## Screen time data
Load the screen time data.
```{r load-screen-data, message=FALSE}
screen_time_raw <- read_csv(here("data", "screen_time_raw.csv"))
```
Alright, the data are extremely wide because of the Qualtrics format.
There are `r ncol(screen_time_raw)` variables in total.
The data look like this:
* Total time over the past week of social media, plus total time per day (`Q2_1` to `Q10_2`)
* Typing in the top ten social networking media (`Q4_1` to `Q4_10`)
* Then participants filled in 13 items for each of those ten apps, first total time and notifications for the week for that app, then hours and minutes per day for that app (total 130 items, `Q3_1` to `Q76_2`)
* Then participants reported six items per each of the ten apps, the first of which was overall pickups in the past week, followed by pickups for each day of the week (`Q17_1_TEXT` to `Q17_10_5`)
Before turning the data into long format, I need to give some sensible variables names.
However, manually renaming all variables is a real pain.
So I'll create the names systematically and store them in a vector, then assign them to the variables.
```{r construct-variable-names}
# define days
days <-
c(
"monday",
"tuesday",
"wednesday",
"thursday",
"friday"
)
# the list of apps
apps <-
paste0(
rep("app_", 10),
1:10
)
# add hours and minutes to apps
apps_times <-
paste0(
rep(apps, each = 6*2),
"_",
c("hours", "minutes")
)
# the overall time per week and day
names1 <-
paste0(
rep(c("hours_total_", "minutes_total_"),6),
rep(c("week", days), each = 2)
)
# now app, minutes, hours, per day
names2 <-
paste0(
apps_times,
"_",
rep(c("week", days), each = 2)
)
# notifications per day
names3 <-
paste0(
apps,
"_",
"notifications",
"_week"
)
# pickups per week and day
names4 <-
paste0(
rep(apps, each = 6),
"_pickups_",
rep(c("week", days), 6)
)
```
Then I apply those names.
Once more, I use a new object and don't overwrite the raw data.
```{r rename-screen-time}
# let's rename
screen_time <-
screen_time_raw %>%
# meta-data
rename(
start_date = StartDate,
end_date = EndDate,
progress = Progress,
duration = Duration__in_seconds_,
finished = Finished,
recorded_date = RecordedDate,
id = study_id
) %>%
# the overall time per week and day
rename_with(
~ names1,
Q2_1:Q10_2
) %>%
# the top ten apps
rename(
app_ = Q4_1:Q4_10
) %>%
# now rename all app minutes and hours per day
rename_with(
~ names2,
c( # except the notifications because they don't fit the pattern
Q3_1:Q76_2,
-Q11_1,
-Q22_1,
-Q78_1,
-Q29_1,
-Q43_1,
-Q36_1,
-Q50_1,
-Q57_1,
-Q64_1,
-Q71_1
)
) %>%
# the past week notifications per app
rename_with(
~ names3,
c(
Q11_1,
Q22_1,
Q78_1,
Q29_1,
Q43_1,
Q36_1,
Q50_1,
Q57_1,
Q64_1,
Q71_1
)
) %>%
# rename pickup variables
rename_with(
~ names4,
Q17_1_TEXT:Q17_10_5
) %>%
# only keep variables of interest
select(
-Status
)
# remove name objects (they were only temp files)
rm(apps, apps_times, days, names1, names2, names3, names4)
```
Before I assign variables types and factor levels, I saw that, for some reason, the past week's number of pickups for each app have text entries, not numerical (i.e., `"twenty one"` instead of `21`).
I know that the `english` package can translate numbers to words, but not the other way around.
Thank god, someone has written a function for this, see full details [here](https://stackoverflow.com/questions/18332463/convert-written-number-to-number-in-r).
The function is in the [Setting up](#setting-up) chapter.
Note that this `word2num` function doesn't work well with the word "and".
For example, `"three hundred and eleven"` turns into ``r word2num("three hundred and eleven")``, but `"three hundred eleven"` turns into the correct ``r word2num("three hundred eleven")``.
Therefore, I first remove all "and"s from those variables, then apply the function.
Similarly, I'll replace all "a" with "one" (e.g., "a hundred" to "one hundred").
One participant also replied with a string (that they access facebook via their browser), so I'll set that to `NA`.
```{r words-to-numbers}
screen_time <-
screen_time %>%
mutate(
across(
ends_with("pickups_week"),
~ case_when(
.x == "NA because accessed Facebook through Safari" ~ NA_character_,
.x == "NA because accessed Twitter through Safari" ~ NA_character_,
TRUE ~ .x
)
),
across(
ends_with("pickups_week"),
~ str_remove(.x, "and")
),
across(
ends_with("pickups_week"),
~ str_replace(.x, "A ", "one ")
)
) %>%
rowwise() %>%
mutate(
across(
ends_with("pickups_week"),
word2num
),
across(
ends_with("pickups_week"),