We load the relevant libraries.
library(jsonlite)
library(tidyverse)
library(dplyr)
library(mousetrap)
library(lmerTest)
library(emmeans)
library(ordinal)
library(readxl)
library(jtools)
library(stats)
library(interactions)
library(ggplot2)
We import the data.
setwd("C:/Users/paolo/Desktop/open projects/RA/The temporal dynamics of interpretative conflict/MT_static/MT_Static_cognition") # Use forward slashes `/` in Windows
pilot_data <- read.csv("paolo-study_1.csv", header = TRUE, sep = ",")
# View(pilot_data) # Opens it in RStudio’s Data Viewer
#head(pilot_data) # Shows first few rows in console
#head(pilot_data$mouse_tracking_data)
We take the x, y and t data and create the columns of xposGET_STIM, yposGET_STIM and timestampsGET_STIM.
# Safe JSON parsing function
safe_parse_json <- function(x) {
if (is.na(x) || x == "") return(NA) # Handle empty values
tryCatch({
parsed <- fromJSON(x)
if (!is.data.frame(parsed)) return(NA) # Ensure valid data frame
parsed
}, error = function(e) NA) # Catch errors
}
pilot_data <- pilot_data %>%
mutate(
parsed_data = map(mouse_tracking_data, safe_parse_json),
xpos_GET_STIM = map(parsed_data, function(x) { if (is.data.frame(x)) x$x else NA }),
ypos_GET_STIM = map(parsed_data, function(x) { if (is.data.frame(x)) x$y else NA }),
timestamps_GET_STIM = map(parsed_data, function(x) { if (is.data.frame(x)) x$t else NA })
) %>%
select(-parsed_data) # Remove intermediate column
# View result
#print(pilot_data)
#head(pilot_data$xpos_GET_STIM)
# Import the data of an old participant to compare the format
old_data <- read.csv("subject-10.csv", header = TRUE, sep = ",")
# View(old_data)
#head(old_data$xpos_GET_STIM)
Put the data of the xposGET_STIM, yposGET_STIM and timestampsGET_STIM columns in the right format (from numeric vector in a list-column to JSON-like string representation).
# Apply `toJSON()` to each numeric vector in the xpos column
pilot_data$xpos_GET_STIM <- sapply(pilot_data$xpos_GET_STIM, function(x) toJSON(x, auto_unbox = TRUE))
# Check the result
#head(pilot_data$xpos_GET_STIM)
# Apply `toJSON()` to each numeric vector in the ypos column
pilot_data$ypos_GET_STIM <- sapply(pilot_data$ypos_GET_STIM, function(x) toJSON(x, auto_unbox = TRUE))
# Check the result
#head(pilot_data$ypos_GET_STIM)
# Apply `toJSON()` to each numeric vector in the timestamps column
pilot_data$timestamps_GET_STIM <- sapply(pilot_data$timestamps_GET_STIM, function(x) toJSON(x, auto_unbox = TRUE))
# Check the result
#head(pilot_data$timestamps_GET_STIM)
Take away all the lines for non-experimental trials (e.g., instructions, practice block).
pilot_data <- pilot_data %>% filter(task == "Draw")
#view(pilot_data)
Define the congruency column and the subject_id one (from run_id).
pilot_data <- pilot_data %>%
mutate(congruency = case_when(
condition %in% c("core", "off") ~ "congruent",
condition %in% c("over", "under") ~ "incongruent",
TRUE ~ NA_character_ # Optional: Assigns NA to other values
))
pilot_data$subject_id <- pilot_data$run_id
#view(pilot_data)
Look if different trials have the same number of samples and look which is the maximum and the minimum number of samples contained by a trial.
# Conta i numeri in ogni cella della colonna xpos_GET_STIM
pilot_data <- pilot_data %>%
mutate(num_count = sapply(xpos_GET_STIM, function(x) length(fromJSON(x))))
# IDENTIFY ROW WITH MINIMUM NUMBER OF SAMPLES
# Find the row with minimum number of samples
min_row <- which.min(pilot_data$num_count)
# PRINT THE MAXIMUM NUMBER OF SAMPLES
# Supponiamo che la cella contenga una stringa JSON-like
json_string <- pilot_data[min_row, "xpos_GET_STIM"] # Change the number to look at a different cell
# Converti la stringa JSON in una lista/vettore R
parsed_data <- fromJSON(json_string)
# Conta il numero di elementi numerici nella lista/vettore
min_samp <- length(parsed_data)
print(min_samp) # Minimum number of samples
## [1] 25
# IDENTIFY ROW WITH MAXIMUM NUMBER OF SAMPLES
# Find the row with maximum number of samples
max_row <- which.max(pilot_data$num_count)
# PRINT THE MAXIMUM NUMBER OF SAMPLES
# Supponiamo che la cella contenga una stringa JSON-like
json_string <- pilot_data[max_row, "xpos_GET_STIM"] # Change the number to look at a different cell
# Converti la stringa JSON in una lista/vettore R
parsed_data <- fromJSON(json_string)
# Conta il numero di elementi numerici nella lista/vettore
max_samp <- length(parsed_data)
print(max_samp) # Maximum number of samples
## [1] 137
# DIFFERENCE BETWEEN MAXIMUM AND MINIMUM NUMBER OF SAMPLES
max_samp-min_samp # If sampling rate is constant across and within trials, it shouldn't be too high
## [1] 112
We load the relevant libraries.
### 01) Import data into mousetrap
mt_data <- mt_import_mousetrap(pilot_data,
xpos_label = "xpos_GET_STIM",
ypos_label = "ypos_GET_STIM",
timestamps_label = "timestamps_GET_STIM")
### 02) Adjustment of the trajectories’ spatial arrangement
# Remap all trajectories to the left
mt_data <- mt_remap_symmetric(mt_data)
# Align trajectories to common start (instead of common end)
mt_data <- mt_align_start(mt_data, start = c(0,0))
### 03) Resampling trajectories
# Time-normalize trajectories
mt_data <- mt_time_normalize(mt_data)
# Length-normalize trajectories
mt_data <- mt_length_normalize(mt_data)
### 06) Prototype filtering
# Top-down prototype mapping (perform top-down mapping onto prototypes)
mt_data <- mt_map(mt_data)
# Determine standardized distance to nearest prototype
mt_data <- mt_standardize(mt_data,
use = "prototyping",
use_variables = "min_dist")
# Visualize anomalous trajectories
mt_plot(mt_data,
use2 = "prototyping",
subset = z_min_dist > 2,
wrap_var = "mt_id")
# Exclude anomalous trajectories
#mt_data <- mt_subset(mt_data,
# z_min_dist <= 2,
# check = "prototyping")
### 07) Compute trajectory indices
mt_data <- mt_measures(mt_data)
### 08) Visually inspect all trials by plotting them in a single figure (plot individual trajectories), with a heatmap of the remapped individual trajectories. What we plot are the raw trajectories, not the temporally- nor the longitudinally-normalised ones:
mt_plot(mt_data,
use = "trajectories",
use2 = "data",
alpha = .1)
mt_plot(mt_data,
use = "trajectories",
use2 = "data",
color = "congruency",
alpha = .1) +
theme_minimal() +
scale_color_manual(values = c('gold', 'navy'),
name = "Congruency", labels = c("Congruent", "Incongruent")) +
labs(x = "Horizontal Position", y = "Vertical Position") +
theme(axis.title.x = element_text(margin = margin(t = 15)),
axis.title.y = element_text(margin = margin(r = 15)))
# With a smoothed heatmap (creating a trajectory heatmap)
mt_heatmap(mt_data, use = "trajectories")
## spatializing trajectories
## calculate image
## smooth image
## enhance image by 40.5
## creating heatmap: 1000 x 611 px
## heatmap created in 5s
# With a difference of smoothed heatmaps between conditions (creating a difference heatmap)
mt_diffmap(mt_data,
condition = "congruency",
smooth_radius = 10, n_shades = 10)
## Determine joint bounds
## Calculating heatmap for x
## Calculating heatmap for y
## smooth image
## creating heatmap: 500 x 305 px
## heatmap created in 4s
mt_plot_aggregate(mt_data, use="tn_trajectories",
x="xpos", y="ypos", color="congruency",
subject_id="subject_id") +
labs(x = "Horizontal Position", y = "Vertical Position") +
scale_color_manual(values = c('#f47081', '#6a9bea'),
name = "Congruency", labels = c("Congruent", "Incongruent")) +
theme(axis.title.x = element_text(margin = margin(t = 15)),
axis.title.y = element_text(margin = margin(r = 15)))
# Look at how the mt_data object is structured
#mt_data$tn_trajectories
#names(mt_data)
#rownames(mt_data$trajectories)
#str(mt_data)
#str(mt_data$trajectories)