Pilot A: Composite Face Task

Author

Seojin Lee

Setup

# install.packages("tidyverse") # only if not already installed
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.1     ✔ stringr   1.5.2
✔ ggplot2   4.0.0     ✔ tibble    3.3.0
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.1.0     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

Load data

data1 <- read_csv("~/Downloads/compositeface_20251026_213322.csv") %>%
mutate(Participant = "P1")
Rows: 705 Columns: 36
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (18): trial_frame, trial_type, plugin_version, Subject, Exp_code, Exp_na...
dbl (16): item_width_mm, item_height_mm, item_width_px, px2mm, view_dist_mm,...
lgl  (2): isPavlovia, Correct

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
data2 <- read_csv("~/Downloads/compositeface_20251026_230456.csv") %>%
mutate(Participant = "P2")
Rows: 705 Columns: 36
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (18): trial_frame, trial_type, plugin_version, Subject, Exp_code, Exp_na...
dbl (16): item_width_mm, item_height_mm, item_width_px, px2mm, view_dist_mm,...
lgl  (2): isPavlovia, Correct

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Combine into one dataset

data_all <- bind_rows(data1, data2)

Preprocess and summarize

df <- data_all %>%
select(-rt) %>%  # remove the lowercase duplicate
filter(trial_frame == "test_face") %>%
rename(
is_aligned = Alignment,
is_congruent = Congruency,
rt = RT
) %>%
select(Participant, is_aligned, is_congruent, Correct, rt)

summary_df <- df %>%
group_by(Participant, is_aligned, is_congruent) %>%
summarise(
mean_acc = mean(Correct, na.rm = TRUE),
mean_rt  = mean(rt, na.rm = TRUE),
n = n(),
.groups = "drop"
)

summary_df
# A tibble: 8 × 6
  Participant is_aligned is_congruent mean_acc mean_rt     n
  <chr>       <chr>      <chr>           <dbl>   <dbl> <int>
1 P1          aligned    congruent       0.807    963.   176
2 P1          aligned    incongruent     0.614    998.   176
3 P1          misaligned congruent       0.716   1049.   176
4 P1          misaligned incongruent     0.665    956.   176
5 P2          aligned    congruent       0.864    705.   176
6 P2          aligned    incongruent     0.597    778.   176
7 P2          misaligned congruent       0.778    770.   176
8 P2          misaligned incongruent     0.744    709.   176

Accuracy Plot

ggplot(summary_df, aes(x = is_congruent, y = mean_acc, fill = is_aligned)) +
geom_bar(stat = "identity", position = position_dodge()) +
facet_wrap(~ Participant) +
labs(
title = "Accuracy by Condition (per Participant)",
x = "Congruency",
y = "Mean Accuracy",
fill = "Alignment"
) +
theme_minimal()

Reaction Time Plot

ggplot(summary_df, aes(x = is_congruent, y = mean_rt, fill = is_aligned)) +
geom_bar(stat = "identity", position = position_dodge()) +
facet_wrap(~ Participant) +
labs(
title = "Reaction Time by Condition (per Participant)",
x = "Congruency",
y = "Mean RT (ms)",
fill = "Alignment"
) +
theme_minimal()