-
Notifications
You must be signed in to change notification settings - Fork 0
/
08-paired.qmd
806 lines (479 loc) · 32.7 KB
/
08-paired.qmd
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
# Paired t-test {#sec-paired}
## Intended Learning Outcomes {.unnumbered}
By the end of this chapter you should be able to:
- Compute a paired t-test and effectively report the results.
- Understand when to use a non-parametric equivalent of the paired t-test, compute it, and report the results.
## [Individual Walkthrough]{style="color: #F39C12; text-transform: uppercase;"} {.unnumbered}
## Activity 1: Setup
In this chapter, we will continue working with the dataset from the study by Zwaan et al. (2018). Have a look at @sec-independent or the SupMats document if you need a refresher on the Simon Task data.
* Open last week's project
* Create a new `.Rmd` file and save it to your project folder
* Delete everything after the setup code chunk (e.g., line 12 and below)
## Activity 2: Library and data for today
Today, we will need the following packages `rstatix`, `tidyverse`, `qqplotr`, `lsr`, and `pwr`. You should have all the necessary packages installed from previous chapters already. If not, you can install them via the console (see @sec-install_packages for more details).
Again, load the `rstatix` package before `tidyverse`. Then read in the data from `MeansSimonTask.csv` and the demographics information from `DemoSimonTask.csv`.
```{r eval=FALSE}
# load in the packages
???
# read in the data
zwaan_data <- ???
zwaan_demo <- ???
```
```{r include=FALSE, message=TRUE}
## I basically have to have 2 code chunks since I tell them to put the data files next to the project, and mine are in a separate folder called data - unless I'll turn this into a fixed path
library(rstatix)
library(tidyverse)
library(qqplotr)
library(lsr)
library(pwr)
zwaan_data <- read_csv("data/MeansSimonTask.csv")
zwaan_demo <- read_csv("data/DemoSimonTask.csv")
```
::: {.callout-caution collapse="true" icon="false"}
## Solution
```{r eval=FALSE}
# load in the packages
library(rstatix)
library(tidyverse)
library(qqplotr)
library(lsr)
library(pwr)
# read in the data
zwaan_data <- read_csv("MeansSimonTask.csv")
zwaan_demo <- read_csv("DemoSimonTask.csv")
```
:::
As usual, take some time to familiarise yourself with the data before starting the within-subjects t-test.
Today, we will focus on the Simon effect itself. Remember that the Simon effect predicts shorter response times for congruent trials compared to incongruent trials. Therefore, we are inclined to propose a directional hypothesis.
* **Potential research question**: "Is there a significant difference in response times between congruent and incongruent trials in a Simon task?"
* **Null Hypothesis (H~0~)**: "There is no significant difference in response times between congruent and incongruent trials in a Simon task."
* **Alternative Hypothesis (H~1~)**: "Response times for congruent trials are significantly shorter than those for incongruent trials in a Simon task." or phrased differently "Participants will respond significantly faster in congruent trials than in incongruent trials in a Simon task."
## Activity 3: Preparing the dataframe
Again, we need to **calculate the mean response time (RT) for both congruent and incongruent trials per participant**. As we did last week, we can also **compute the Simon effect** as the difference score between incongruent and congruent trials.
::: {.callout-tip}
To keep all the data in one place, we should join this output with the demographics. While you won’t need the demographic information for the t-test itself, having it included will give you a complete dataframe. This can be useful when you need to calculate demographics for the Methods section; for example if you end up excluding data points, you can compute sample size, age, and gender splits straight away rather than having to apply the same exclusion criteria to a different data object.
:::
For the paired version of the `t.test`, we need the congruent and incongruent trials in separate columns, ensuring **each participant still has only one row in the dataframe** (i.e., **wide format**). Below is the output we aim to achieve:
```{r echo=FALSE, warning=FALSE, message=FALSE}
simon_effect <- zwaan_data %>%
pivot_longer(cols = session1_congruent:session2_incongruent, names_to = "col_headings", values_to = "RT") %>%
separate(col_headings, into = c("Session_number", "congruency"), sep = "_") %>%
group_by(participant, similarity, congruency) %>%
summarise(mean_RT = mean(RT)) %>%
ungroup() %>%
pivot_wider(names_from = congruency, values_from = mean_RT) %>%
mutate(simon_effect = incongruent - congruent) %>%
full_join(zwaan_demo, by = join_by(participant == twosubjectnumber)) %>%
select(participant, gender = gender_response, age = age_response, education = education_response, similarity:simon_effect)
head(simon_effect, n = 5)
```
::: {.callout-note icon="false"}
## Your Turn
Challenge yourself! Try recreating the table without hints this time. *(Note: The table above is abbreviated; your data object should include the data from ALL participants.)*
::: {.callout-caution collapse="true" icon="false"}
## Solution
```{r eval=FALSE}
simon_effect <- zwaan_data %>%
pivot_longer(cols = session1_congruent:session2_incongruent, names_to = "col_headings", values_to = "RT") %>%
separate(col_headings, into = c("Session_number", "congruency"), sep = "_") %>%
group_by(participant, similarity, congruency) %>%
summarise(mean_RT = mean(RT)) %>%
ungroup() %>%
pivot_wider(names_from = congruency, values_from = mean_RT) %>%
mutate(simon_effect = incongruent - congruent) %>%
full_join(zwaan_demo, by = join_by(participant == twosubjectnumber)) %>%
select(participant, gender = gender_response, age = age_response, education = education_response, similarity:simon_effect)
```
:::
::: {.callout-tip collapse="true"}
## You could have also...
1. looked at the hints from the last chapter, since we are working with the exact same dataframe today.
2. saved the data object from last week as a csv file and read it in here.
:::
:::
## Activity 4: Compute descriptives
We want to compute means and standard deviations for both the `congruent` and the `incongruent` trials. Then, we will subtract the mean RT of the congruent trials from the mean RT of the incongruent trials to calculate the average difference score between the two conditions.
```{r}
descriptives <- simon_effect %>%
summarise(mean_congruent = mean(congruent),
sd_congruent = sd(congruent),
mean_incongruent = mean(incongruent),
sd_incongruent = sd(incongruent),
diff = mean_incongruent - mean_congruent, # diff = mean(simon_effect) would also work
sd_diff = sd(simon_effect))
```
```{r echo=FALSE}
descriptives
```
::: {.callout-note}
Notice that we did not have to use `group_by()` here because the data is already in wide format.
:::
## Activity 5: Create an appropriate plot
### Option 1: congruent and incongruent trials
To create an appropriate plot, we need the data in long format. This format should include a `congruency` column containing the labels for congruent and incongruent trials, and a `mean_RT` column to store the corresponding mean response times. **Each participant should now have two rows in the dataset.**
```{r echo=FALSE, warning=FALSE, message=FALSE}
simon_effect_long <- simon_effect %>%
pivot_longer(cols = c(congruent, incongruent), names_to = "congruency", values_to = "mean_RT")
head(simon_effect_long, n = 5)
```
::: {.callout-note icon="false"}
## Your Turn
First, wrangle data into long format. The first 5 rows should match the output above. Then, use this table to create an appropriate plot that visualises the differences in mean response times between congruent and incongruent trials.
::: {.callout-caution collapse="true" icon="false"}
## Solution for the data
```{r}
simon_effect_long <- simon_effect %>%
pivot_longer(cols = c(congruent, incongruent), names_to = "congruency", values_to = "mean_RT")
```
:::
::: {.callout-caution collapse="true" icon="false"}
## Solution for the plot
```{r}
ggplot(simon_effect_long, aes(x = congruency, y = mean_RT, fill = congruency)) +
geom_violin(alpha = 0.5) +
geom_boxplot(width = 0.4, alpha = 0.8) +
scale_fill_viridis_d(guide = "none") +
theme_classic() +
labs(x = "Congruency", y = "mean Response Time")
```
:::
:::
### Option 2: difference scores between congruent and incongruent trials
We could have displayed the difference scores between the two conditions directly. This approach would use the `simon_effect` object without requiring additional data wrangling, as the difference scores are already stored in the `simon_effect` column.
::: {.callout-note collapse="true" icon="false"}
## Hints for the plot
We want to create a violin-boxplot, but this time it will have only one value on the x-axis instead of a grouping variable as we usually include. This causes the `ggplot()` function to behave slightly differently. Specifically, you would leave the x-value blank.
```{r eval=FALSE}
ggplot(data, aes(x = "", y = continuous_variable)) +
```
To add colour, use the usual arguments (e.g., `fill`, `colour`), but specify them within the `geom_` functions instead of `ggplot()`, as we typically do.
:::
::: {.callout-caution collapse="true" icon="false"}
## Solution for the plot
```{r}
ggplot(simon_effect, aes(x = "", y = simon_effect)) +
geom_violin(fill = "#A3319F", alpha = 0.5) +
geom_boxplot(fill = "#A3319F", width = 0.4) +
theme_classic() +
labs(x = "",
y = "Difference in mean Response Time scores")
```
:::
## Activity 6: Check assumptions
The assumptions for a paired t-test are fairly similar to the one-sample t-test.
#### Assumption 1: Continuous DV {.unnumbered}
The dependent variable needs to be measured at interval or ratio level. We can confirm that by looking at either the columns `congruent` and `incongruent` in the object `simon_effect`. Or the variable `mean_RT` in `simon_effect_long`. This assumption holds.
#### Assumption 2: Data are independent {.unnumbered}
For a paired t-test this assumption applies to the **pair of values**, i.e., each pair of values needs to be from a separate participant. We assume this assumption holds for our data.
#### Assumption 3: Normality {.unnumbered}
This assumption requires the **difference scores** to be approximately normally distributed. We cannot see that from the violin-boxplot above (Option 1) and have to plot the difference score, i.e., variable `simon_effect`.
::: {.callout-note icon="false"}
## Your Turn
Plot the difference score.
::: {.callout-note collapse="true" icon="false"}
## Hint
Think back to the one-sample t-test. How did we plot the normality assumption there?
:::
::: {.callout-caution collapse="true" icon="false"}
## Solution
Explore the three possible approaches in the tabs below.
::: {.panel-tabset}
## Option 1: Q-Q plot
```{r}
ggplot(simon_effect, aes(sample = simon_effect)) +
stat_qq_band(fill = "#FB8D61", alpha = 0.4) +
stat_qq_line(colour = "#FB8D61") +
stat_qq_point()
```
## Option 2: Violin-boxplot of the difference score
You can use the plot from above if you chose Option 2 in Activity 5, or create a new violin-boxplot if you created plot Option 1 above.
```{r}
ggplot(simon_effect, aes(x = "", y = simon_effect)) +
geom_violin(fill = "#FB8D61", alpha = 0.5) + # alpha for opacity, fill for adding colour
geom_boxplot(fill = "#FB8D61", width = 0.4) + # change width of the boxes
theme_classic() +
labs(x = "",
y = "Difference in mean Response Time scores")
```
## Option 3: Shapiro-Wilk test
```{r}
shapiro.test(simon_effect$simon_effect)
```
:::
Both plots suggest slight deviations from normality in the tails, which are not detected by the Shapiro-Wilk test. However, minor deviations from normality are acceptable for t-tests, especially in the tails of the distribution. Therefore, we conclude that the difference scores are approximately normally distributed.
:::
:::
::: {.callout-note}
Assessing normality is never as straightforward as it appears in textbooks. While idealised examples often depict perfectly symmetrical bell curves, real-world data rarely aligns with such precision. Instead, normality exists "on a spectrum", and it is your responsibility to evaluate whether the data’s shape is sufficiently close to normal for the purposes of your analysis.
This evaluation requires either visual inspection or relying on statistical tests. However, as pointed out in previous chapters, both approaches have limitations and neither provides an absolute answer. Instead of seeking perfection, focus on whether any deviations from normality meaningfully impact the validity of your results.
:::
**If any of the assumptions are violated, use the non-parametric equivalent to the paired t-test, see @sec-alternative_paired.**
## Activity 7: Compute a paired t-test and effect size
We can use the `t.test()` function again to compute the paired t-test. However, we are stuck with the BaseR pattern `data$column` once more.
::: {.callout-note collapse="true"}
## Rant about BaseR and reminiscing about the "good old days"
In case you haven't picked it up by now, I am not much a fan of `data$column` (i.e., wide format) and prefer the `DV ~ IV` (i.e., long format) pattern. And there was a time when the `t.test()` function allowed to add an extra argument `paired = TRUE` to the *formula version* but that is no longer the case. 😭😭😭 Now, the argument *only* works on the default method, specifying arguments x and y separately. And because the default version doesn't allow us to add a `data = ` argument, we have to revert to `data$column`.
PS: Carolina disagrees with me and prefers some BaseR every now and then. And that's totally fine, too - different researchers and coders have their own preferences, which you may have already noticed in the way you code.
:::
Long story short, here are the arguments you need from the data object in wide format (in this case data object `simon_effect`):
* `data$column` for condition 1
* `data$column` for condition 2
* the extra argument `paired = TRUE` to tell the function we are conducting a paired rather than a two-sample t-test
```{r}
t.test(simon_effect$congruent, simon_effect$incongruent, paired = TRUE)
```
The output tells us pretty much what we need to know:
* the test that was conducted (here a paired t-test),
* the conditions that were compared (here congruent and incongruent),
* the t-value, degrees of freedom, and a p-value,
* the alternative hypothesis,
* a 95% confidence interval,
* and the mean difference score between both conditions (which also matches with our descriptives above).
The `t.test()` function does not give us an **effect size**, so, again, we have to compute it ourselves. We can use the `CohensD()` function from the `lsr` package as we did for the one-sample and the two-sample t-test. We can use the formula approach here as well, and add the extra argument `method = "paired"`.
```{r}
cohensD(simon_effect$congruent, simon_effect$incongruent, method = "paired")
```
::: {.callout-tip}
The `cohensD()` function would also take a long format formula approach, such as from `simon_effect_long`, but you would need to make sure that the data within the columns are ordered correctly, i.e., Participant 1: condition 1, condition 2; Participant 2: condition 1, condition 2; etc. If that's not the case, then your results will be wrong. R helpfully provides a warning message for you to notice and double-check your data accordingly.
```{r}
cohensD(mean_RT ~ congruency, data = simon_effect_long, method = "paired")
```
:::
## Activity 8: Sensitivity power analysis
As with the other t-test, we are conducting a sensitivity power analysis to determine the minimum effect size detectable with the number of participants we have in our sample (here n = 160), alpha of 0.05 and power of 0.8. This will tell us if our analysis was sufficiently powered or not.
The function we will use again is `pwr.t.test()` from the `pwr` package. The arguments in the formula are the same as for the one-sample t-test; we just need to adjust the number of participants and set the type to "paired".
```{r}
pwr.t.test(n = 160, sig.level = 0.05, power = 0.8, type = "paired", alternative = "two.sided")
```
The minimum effect size we could reliably detect is 0.22. Our actual effect size was 1.59, so this analysis was sufficiently powered. YAY
## Activity 9: The write-up
We hypothesised that there would be a significant difference in the response times between congruent $(M = 427.65 msec, SD = 74.17 msec)$ and incongruent trials $(M = 462.08 msec, SD = 74.70 msec)$ of a Simon task. On average, participants were faster in the congruent compared to the incongruent condition $(M_{diff} = 34.43 msec, SD_{diff} = 21.60 msec)$. Using a within-subjects t-test, the effect was found to be significant and of a large magnitude, $t(159) = 20.16, p < .001, d = 1.59$. Therefore, we reject the null hypothesis in favour of H~1~.
## Activity 10: Non-parametric alternative {#sec-alternative_paired}
The **Wilcoxon signed-rank test** is the non-parametric equivalent to the paired t-test, comparing the difference between the median for two measurements.
Before we compute the test, we need to determine some **summary stats** (e.g., here we focus on the median scores) for the congruent and incongruent conditions. Similar to the One-sample Wilcoxon signed-rank test, we can use the `summary()` function again because our data is in wide format.
```{r}
summary(simon_effect)
```
Now we can move on to the **Wilcoxon signed-rank test**. We will use the `wilcox.test()` function again, but add the argument `paired = TRUE`.
```{r}
wilcox.test(simon_effect$congruent, simon_effect$incongruent, paired = TRUE)
```
::: {.callout-note}
We could have also run a **One-sample Wilcoxon signed-rank test** on the difference score, but instead of comparing that to a population median (as we did in @sec-alternative_one_sample), we would compare it to 0 to assess whether the median difference between paired observations is significantly different from zero.
```{r}
wilcox.test(simon_effect$simon_effect, mu = 0)
```
::: {.callout-important}
## The V values are different between the 2 approaches - Does that matter?
Yes and no. The order in which you input variables into the function will affect the value of V - has to do with how the ranks are getting assigned. In our column `simon_effect` we subtracted congruent RT from incongruent RT. To have the exact equivalent, we will need to switch the columns in the `wilcox.test()` function.
```{r}
wilcox.test(simon_effect$incongruent, simon_effect$congruent, paired = TRUE)
```
And as you can see, the V values match now.
:::
:::
We should also compute the **standardised test statistic Z**. Again, we need to calculate Z manually, using the qnorm function on the halved p-value from our Wilcoxon test above.
```{r}
# storing the p-value
p_wilcoxon <- wilcox.test(simon_effect$incongruent, simon_effect$congruent, paired = TRUE)$p.value
# calculate the z value from half the p-value
z = qnorm(p_wilcoxon/2)
z
```
To calculate an **effect size**, we would need to use the function `wilcox_effsize()` from the `rstatix` package. Unlike, the `wilcox.test()` and the `t.test()` function, `wilcox_effsize()` expects data to be in **long format** to be able to use the `DV ~ IV` pattern. Fortunately, we still have that available in `simon_effect_long`. We also need to add the argument `paired = TRUE`.
```{r}
wilcox_effsize(data = simon_effect_long, formula = mean_RT ~ congruency, paired = TRUE)
```
Now we have all the numbers to **write this up**:
A Wilcoxon signed-rank test was conducted to determine whether there was a significant difference in response times between congruent $(Mdn = 411.3 msec)$ and incongruent trials $(Mdn = 449.7 msec)$ in a Simon task. Median response times of Congruent trials were significantly faster $(Mdn = 34.77 msec)$ than incongruent trials, $Z = -10.73, p < .001, r = .848$. The difference can be classified as large according to Cohen (1992). Therefore, we reject the null hypothesis in favour of H~1~.
## [Pair-coding]{style="color: #F39C12; text-transform: uppercase;"} {.unnumbered}
::: {.callout-important}
This week's pair-coding activity mirrors the one from @sec-independent. While there is no lab scheduled this week, I wanted to provide an opportunity for you to practice. You can treat this either as a "Challenge Yourself" activity or get together with friends to work through it together.
:::
### Task 1: Open the R project for the lab {.unnumbered}
### Task 2: Create a new `.Rmd` file {.unnumbered}
... and name it something useful. If you need help, have a look at @sec-rmd.
### Task 3: Load in the library and read in the data {.unnumbered}
The data should already be in your project folder. If you want a fresh copy, you can download the data again here: [data_pair_coding](data/data_pair_coding.zip "download").
We are using the packages `rstatix`, `tidyverse`, `qqplotr`, `lsr` today.
Make sure to load `rstatix` in before `tidyverse`.
We also need to read in `dog_data_clean_wide.csv`. Again, I've named my data object `dog_data_wide` to shorten the name but feel free to use whatever object name sounds intuitive to you.
For the plot, we will need the data in long format. We can either read in `dog_data_clean_long.csv` to take a shortcut, or wrangle the data from `dog_data_wide`. I've taken the shortcut and named my data object `dog_data_long`.
```{r reading in data for me, echo=FALSE, message=FALSE}
library(rstatix)
library(tidyverse)
library(lsr)
dog_data_wide <- read_csv("data/dog_data_clean_wide.csv")
dog_data_long <- read_csv("data/dog_data_clean_long.csv")
```
### Task 4: Tidy data for a paired t-test {.unnumbered}
Not much tidying to do for today.
Pick a variable of interest and select the pre- and post-scores, and calculate the difference score. Store them in a separate data object with a meaningful name.
I will use Loneliness as an example and call my data object `dog_lonely`. Regardless of your chosen variable, your data object should look like/ similar to the table below.
```{r echo=FALSE, warning=FALSE, message=FALSE}
dog_lonely <- dog_data_wide %>%
select(RID, Loneliness_pre, Loneliness_post) %>%
mutate(Loneliness_diff = Loneliness_post - Loneliness_pre)
head(dog_lonely, n = 5)
```
In `dog_data_long`, we want to turn `Stage` into a factor so we can re-order the labels (i.e., "pre" before "post").
::: {.callout-caution collapse="true" icon="false"}
## Solution for Tasks 3 and 4
```{r}
## Task 3
library(rstatix)
library(tidyverse)
library(lsr)
dog_data_wide <- read_csv("data/dog_data_clean_wide.csv")
dog_data_long <- read_csv("data/dog_data_clean_long.csv")
## Task 4
dog_lonely <- dog_data_wide %>%
select(RID, Loneliness_pre, Loneliness_post) %>%
mutate(Loneliness_diff = Loneliness_post - Loneliness_pre)
dog_data_long <- dog_data_long %>%
mutate(Stage = factor(Stage,
levels = c("pre", "post")))
```
:::
### Task 5: Compute descriptives {.unnumbered}
We want to determine the mean and sd of:
* the pre-scores
* the post-scores, and
* the difference scores
Store them in a data object called `descriptives`.
::: {.callout-caution collapse="true" icon="false"}
## Solution
```{r}
descriptives <- dog_lonely %>%
summarise(mean_pre = mean(Loneliness_pre),
sd_pre = sd(Loneliness_pre),
mean_post = mean(Loneliness_post),
sd_post = sd(Loneliness_post),
diff = mean(Loneliness_diff),
sd_diff = sd(Loneliness_diff))
```
:::
### Task 6: Check assumptions {.unnumbered}
#### Assumption 1: Continuous DV {.unnumbered}
Is the dependent variable (DV) continuous? Answer: `r longmcq(sample(c(answer = "Yes. The DV is the difference in loneliness scores and it is continuous", x = "No. The DV is the difference in loneliness scores but it is categorical", x = "No. The DV is the pre- and post-stages and it is categorical")))`
#### Assumption 2: Data are independent {.unnumbered}
Each pair of values in the dataset has to be independent, meaning each pair of values needs to be from a separate participant. Answer: `r mcq(c(answer = "yes", x = "no"))`
#### Assumption 3: Normality {.unnumbered}
Looking at the violin-boxplots below, do you think the assumption of normality holds?
::: {.callout-note}
The labels of Plot 2 turned out to be quite long here. I've used the escape character `\n` here. to break up the title across 2 lines.
:::
```{r eval = FALSE, message = FALSE}
## Plot 1
ggplot(dog_data_long, aes(x = Stage, y = Loneliness, fill = Stage)) +
geom_violin(alpha = 0.5) +
geom_boxplot(width = 0.4, alpha = 0.8) +
scale_fill_viridis_d(guide = "none") +
theme_classic() +
labs(x = "Time point", y = "mean Loneliness Scores")
## Plot 2
ggplot(dog_lonely, aes(x = "", y = Loneliness_diff)) +
geom_violin(fill = "#21908C", alpha = 0.5) +
geom_boxplot(fill = "#21908C", width = 0.4) +
theme_classic() +
labs(x = "",
y = "Difference in mean Loneliness scores \nbetween pre- and post- intervention") # \n forces a manual line break in the axis label
```
```{r fig.cap="Plots displayed to assess normality assumption", echo=FALSE}
library(patchwork)
## Plot 1
p1 <- ggplot(dog_data_long, aes(x = Stage, y = Loneliness, fill = Stage)) +
geom_violin(alpha = 0.5) +
geom_boxplot(width = 0.4, alpha = 0.8) +
scale_fill_viridis_d(guide = "none") +
theme_classic() +
labs(x = "Time point", y = "mean Loneliness Scores") +
ggtitle("Plot 1")
## Plot 2
p2 <- ggplot(dog_lonely, aes(x = "", y = Loneliness_diff)) +
geom_violin(fill = "#21908C", alpha = 0.5) +
geom_boxplot(fill = "#21908C", width = 0.4) +
theme_classic() +
labs(x = "",
y = "Difference in mean Loneliness scores \nbetween pre- and post- intervention") +
ggtitle("Plot 2")
p1 + p2 + plot_layout(nrow = 1)
```
Answer:
`r longmcq(c(x = "yes, because both pre- and post-scores in Plot 1 are approximately normally distributed", answer = "yes, because the difference scores in Plot 2 are approximately normally distributed", x = "no, because both pre- and post-scores in Plot 1 are extremely skewed", x = "no, because the difference scores in Plot 2 are extremely skewed"))`
#### Conclusion from assumption tests {.unnumbered}
With all assumptions tested, which statistical test would you recommend for this analysis?
Answer: `r longmcq(c(answer = "All assumptions held. We will conduct a paired-samples t-test.", x = "The assumption of normality was violated. We will conduct a Wilcoxon signed-rank test."))`
### Task 7: Computing a paired-sample t-test with effect size & interpret the output {.unnumbered}
* **Step 1**: Compute the paired-sample t-test. The structure of the function is as follows:
```{r eval=FALSE}
t.test(your_data$var1, your_data$var2, paired = TRUE)
```
::: {.callout-caution collapse="true" icon="false"}
## Solution
```{r eval=FALSE}
t.test(dog_lonely$Loneliness_pre, dog_lonely$Loneliness_post, paired = TRUE)
```
:::
* **Step 2**: Calculate an effect size
Calculate Cohen's D. The structure of the function is as follows:
```{r eval=FALSE}
cohensD(your_data$var1, your_data$var2, method = "paired")
```
::: {.callout-caution collapse="true" icon="false"}
## Solution
```{r eval=FALSE}
cohensD(dog_lonely$Loneliness_pre, dog_lonely$Loneliness_post, method = "paired")
```
:::
* **Step 3**: Interpreting the output
Below are the outputs for the **descriptive statistics** (table), **paired-samples t-test** (main output), and **Cohen’s D** (last line starting with [1]). Based on these, write up the results in APA style and provide an interpretation.
```{r echo=FALSE}
descriptives
t.test(dog_lonely$Loneliness_pre, dog_lonely$Loneliness_post, paired = TRUE)
cohensD(dog_lonely$Loneliness_pre, dog_lonely$Loneliness_post, method = "paired")
```
We hypothesised that there would be a significant difference between Loneliness measured before (M = `r fitb("2.04")`, SD = `r fitb("0.53")`) and after (M = `r fitb("1.91")`, SD = `r fitb("0.53")`) the dog intervention. On average, participants felt less lonely after the intervention (M{diff} = `r fitb("0.13")`, SD{diff} = `r fitb("0.23")`).
Using a paired-samples t-test, the effect was found to be `r mcq(c(answer = "significant", x = "non-significant"))` and of a `r mcq(c(answer = "small", x = "medium", x = "large"))` magnitude, t(`r fitb("283")`) = `r fitb("9.26")`, p `r fitb("< .001")`, d = `r fitb("0.55")`. We therefore `r mcq(c(x = "fail to reject the null hypothesis", answer = "reject the null hypothesis in favour of H~1~"))`.
## [Test your knowledge]{style="color: #F39C12; text-transform: uppercase;"} {.unnumbered}
#### Question 1 {.unnumbered}
**What is the main purpose of an paired-samples t-test?**
`r longmcq(sample(c(x = "To compare means between two independent groups", answer = "To compare means between two related groups", x = "To assess the correlation between two continuous variables", x = "To test for differences in variances between two independent groups")))`
::: {.callout-caution collapse="true" icon="false"}
## Explain this answer
The paired t-test is used when comparing means from related groups. It requires two measurements from the same group or individual. Examples could be before-and-after measurements (e.g., pre- and post-intervention),
or repeated measures (e.g., exercise under 2 different conditions, such as indoor and outdoor), etc.
:::
#### Question 2 {.unnumbered}
**Which of the following is a key assumption of the paired t-test?**
`r longmcq(sample(c(answer = "The differences between paired observations are normally distributed.", x = "The two samples are independent.", x = "The dependent variable is categorical.", x = "The two groups have equal variances.")))`
::: {.callout-caution collapse="true" icon="false"}
## Explain this answer
The paired t-test assumes that the differences between the paired observations are normally distributed, as the test is applied to these differences, not the raw scores.
:::
#### Question 3 {.unnumbered}
**What is the null hypothesis for a paired t-test?**
`r longmcq(sample(c(answer = "The differences between paired observations are equal to zero.", x = "The means of two independent groups are equal.", x = "The variances of the two samples are equal.", x = "There is no relationship between two variables.")))`
::: {.callout-caution collapse="true" icon="false"}
## Explain this answer
The paired t-test specifically tests whether the mean difference between paired observations (e.g., pre-test and post-test scores) is zero, indicating no systematic difference.
Why the other options are incorrect:
* **The means of two independent groups are equal**: This describes the null hypothesis for an independent t-test, not a paired t-test. The paired t-test is used for related groups or repeated measures, not independent groups.
* **The variances of the two samples are equal**: This is an assumption that applies to the independent t-test, which compares two unrelated groups. The paired t-test does not test variances; it focuses on the differences between paired observations.
* **There is no relationship between two variables**: This is not the null hypothesis for a paired t-test. It relates more to correlation or regression analyses, which assess relationships between variables, not mean differences.
:::
#### Question 4 {.unnumbered}
**A paired-samples t-test was conducted to compare sleep quality scores with (M = 7.5, SD = 1.8) and without (M = 6.0, SD = 1.9) white noise, t(29) = 4.55, p < .001, d = 0.82. Answer the questions below.**
* How many participants were included in the study? `r fitb("30")`
* What is the effect size, and how would you describe its magnitude?
The effect size is `r fitb("0.82")` and of `r mcq(c(x = "small", x = "medium", answer = "large"))` magnitude.
* Is the result statistically significant? `r mcq(sample(c(answer = "The result is statistically significant because p < .001", x = "The result is statistically non-significant because p < .001")))`
* Based on the means and standard deviations, how would you summarise the direction of the effect?
`r longmcq(sample(c(answer = "Sleep quality was higher with white noise than without white noise.", x = "Sleep quality was higher without white noise than with white noise.", x = "Sleep quality was the same in both conditions.", x = "The direction of the effect cannot be determined from the output.")))`
::: {.callout-caution collapse="true" icon="false"}
## Explain these answers
The degrees of freedom for a paired-samples t-test are calculated as N - 1 for the number of paired observations. With df = 29, the sample size is 30 participants.
An effect size of d = 0.82 is considered large. This suggests a meaningful and substantial difference in sleep quality between the two conditions.
The result is statistically significant. The p-value (p < .001) indicates that the observed difference in the sample is very unlikely to occur if we assume there is no true difference in the population.
The mean sleep quality score with white noise (M = 7.5) is higher than without white noise (M = 6.0), indicating improved sleep quality with white noise.
:::