DATA IMPORT & REFORMATTING

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

MOUSETRAP PREPROCESSING & ANALYSIS

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)