This tutorial illustrates how to use the mousetrap package to process, analyze and visualize movement trajectories. It accompanies the paper of the same name.
The R code in this tutorial performs all analyses and creates all trajectory data related figures that are presented in the paper.
After running the initialization section, all subsequent sections are self-contained. That is, each section can be run independently, as it prepares the data needed for its analysis and plot.
library(mousetrap)
library(tidyverse)
library(patchwork)
library(psych)
library(MBESS)
library(viridis)
library(afex)
library(osfr)
theme_set(theme_minimal())
custom_colors <- cividis(5)
custom_colors_3 <- custom_colors[c(1, 4, 5)]
# Import and preprocess mouse-tracking data
mt_data <- KH2017_raw %>%
filter(correct == 1) %>%
mt_import_mousetrap() %>%
mt_remap_symmetric() %>%
mt_align_start(start = NULL) %>%
mt_subset(mt_id %in% c("id0013", "id0030", "id0033"))
# Specify coordinates of buttons that should be plotted
rectangles <- matrix(
c(
-840, 525, 350, -170,
840, 525, -350, -170
),
ncol = 4, byrow = TRUE
)
# Specify button labels
button_labels <- data.frame(
label = c("Whale", "Mammal", "Fish"),
xpos = c(0, -665, 665),
ypos = c(-440 + 85 + 40, 440, 440)
)
mt_plot(mt_data, return_type = "mapping") +
mt_plot_add_rect(rectangles) +
coord_cartesian(xlim = c(-840, 840), ylim = c(-525, 525), expand = FALSE) +
geom_path(aes(color = mt_id), size = 2) +
geom_text(
aes(x = xpos, y = ypos, label = label), data = button_labels, size = 3.2
) +
theme(legend.position = "none") +
scale_color_manual(values = custom_colors_3) +
labs(x = NULL, y = NULL)
mt_data <- KH2017_raw %>%
mt_import_mousetrap()
summary(mt_count(mt_data$trajectories))
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 72.0 133.0 169.0 206.3 225.0 2159.0
summary(mt_data$data$response_time)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 703 1320 1672 2049 2234 21577
# Import and preprocess mouse-tracking data
mt_data <- KH2017_raw %>%
filter(correct == 1) %>%
mt_import_mousetrap() %>%
mt_remap_symmetric(remap_xpos = "no") %>%
mt_subset(mt_id %in% c("id0013", "id0030", "id0033"))
# Only select every second trajectory position
mt_data$trajectories <- mt_data$trajectories[, seq(1, 151, 2), ]
# Plot raw trajectories
p1 <- mt_plot(mt_data, return_type = "mapping") +
geom_path(color = "white", alpha = 1, size = .3, show.legend = FALSE) +
geom_path(aes(color = mt_id), alpha = .7, size = .3, show.legend = FALSE) +
geom_point(color = "white", alpha = 1, show.legend = FALSE) +
geom_point(aes(color = mt_id), alpha = .7, show.legend = FALSE) +
coord_cartesian(xlim = c(-840, 840), ylim = c(-525, 525), expand = FALSE) +
scale_color_manual(values = custom_colors_3) +
labs(x = NULL, y = NULL) +
theme(axis.text.x = element_blank())
# Remap trajectories, align their start position and plot them
mt_data <- mt_remap_symmetric(mt_data)
mt_data <- mt_align_start(mt_data, start = NULL)
p2 <- mt_plot(mt_data, return_type = "mapping") +
geom_path(color = "white", alpha = 1, size = .3, show.legend = FALSE) +
geom_path(aes(color = mt_id), alpha = .7, size = .3, show.legend = FALSE) +
geom_point(color = "white", alpha = 1, show.legend = FALSE) +
geom_point(aes(color = mt_id), alpha = .7, show.legend = FALSE) +
coord_cartesian(xlim = c(-840, 840), ylim = c(-525, 525), expand = FALSE) +
scale_color_manual(values = custom_colors_3) +
labs(x = NULL, y = NULL) +
theme(axis.text.x = element_blank(), axis.text.y = element_blank())
# Time normalize and plot trajectories
mt_data <- mt_time_normalize(mt_data)
p3 <- mt_plot(mt_data, use = "tn_trajectories", return_type = "mapping") +
geom_path(color = "white", alpha = 1, size = .3, show.legend = FALSE) +
geom_path(aes(color = mt_id), alpha = .7, size = .3, show.legend = FALSE) +
geom_point(color = "white", alpha = 1, show.legend = FALSE) +
geom_point(aes(color = mt_id), alpha = .7, show.legend = FALSE) +
coord_cartesian(xlim = c(-840, 840), ylim = c(-525, 525), expand = FALSE) +
scale_color_manual(values = custom_colors_3) +
labs(x = NULL, y = NULL)
# Length normalize and plot trajectories
mt_data <- mt_length_normalize(mt_data)
p4 <- mt_plot(mt_data, use = "ln_trajectories", return_type = "mapping") +
geom_path(color = "white", alpha = 1, size = .3, show.legend = FALSE) +
geom_path(aes(color = mt_id), alpha = .7, size = .3, show.legend = FALSE) +
geom_point(color = "white", alpha = 1, show.legend = FALSE) +
geom_point(aes(color = mt_id), alpha = .7, show.legend = FALSE) +
coord_cartesian(xlim = c(-840, 840), ylim = c(-525, 525), expand = FALSE) +
scale_color_manual(values = custom_colors_3) +
labs(x = NULL, y = NULL) +
theme(axis.text.y = element_blank())
(p1 + p2) / (p3 + p4) & plot_annotation(tag_levels = "A")
# Preprocess mouse-tracking data
mt_data <- KH2017 %>%
mt_time_normalize() %>%
mt_length_normalize() %>%
mt_map() %>%
mt_standardize(use = "prototyping", use_variables = "min_dist")
# Classify outliers
mt_data$data$outlier <- ifelse(
mt_data$prototyping$z_min_dist > 2,
"Distance > 2 SD", "Distance <= 2 SD"
)
mt_plot(mt_data, color = "outlier", return_type = "mapping") +
geom_path(aes(alpha = outlier)) +
theme(legend.position = c(.14, .2), legend.background = element_blank()) +
scale_color_manual(name = "", values = custom_colors[c(3, 1)]) +
scale_alpha_manual(name = "", values = c(.08, .6)) +
labs(x = NULL, y = NULL) +
coord_cartesian(xlim = c(-950, 950), ylim = c(-150, 1050), expand = FALSE)
# Import and preprocess mouse-tracking data and compute indices
mt_data <- KH2017_raw %>%
filter(correct == 1) %>%
mt_import_mousetrap() %>%
mt_remap_symmetric() %>%
mt_align_start() %>%
mt_time_normalize() %>%
mt_derivatives() %>%
mt_measures() %>%
mt_sample_entropy()
# Calculate movement time
mt_data$measures$movement <- mt_data$measures$RT - mt_data$measures$idle_time
# Calculate motor pauses
mt_data$measures$motor_pauses <-
mt_data$measures$idle_time - mt_data$measures$initiation_time
# Select measures and specify labels
measures <- mt_data$measures[, c(
"MAD", "MD_above", "AD", "AUC", "xpos_flips",
"xpos_reversals", "sample_entropy", "RT", "initiation_time",
"motor_pauses", "movement"
)]
labels <- c(
"MAD", expression(MD[above]), "AD", "AUC", "Flips",
"Reversals", "Sample entropy", "RT", "Initiation time",
"Motor pauses", "Movement"
)
# Compute Pearson and Spearman rank correlations
cors <- measures %>% cor()
cors_rank <- measures %>% cor(method = "spearman")
# Compute Cohen's d with confidence interval per measure
cond <- mt_data$data$Condition
effects <- sapply(measures, function(x) cohen.d(x, cond == "Atypical")$cohen.d)
# Setup colored correlations grid
cors_combined <- cors
cors_combined[upper.tri(cors_combined)] <- cors_rank[upper.tri(cors_combined)]
cols <- rev(cividis(201))
offset <- c(rep(0, 4), rep(.5, 3), rep(1, 4))
pos <- expand.grid(x = (1:11) + offset, y = (1:11) + offset) %>%
mutate(
cor = as.vector(t(cors_combined)),
col = (cols)[round(c(cor) * 100) + 101]
) %>%
filter(x != y) %>%
mutate(
y = 1 + max(y) - y
)
# Setup two pane plot layout
layout(matrix(1:2, ncol = 2), width = c(.6, .4))
par(mar = c(1, 5, 5, 1))
# Create correlations plot
w <- .5
plot.new()
plot.window(xlim = range(pos$x) + c(-w, w), ylim = range(pos$y) + c(-w, w))
rect(
pos$x - w, pos$y - w, pos$x + w, pos$y + w,
col = pos$col, border = NA
)
text(pos$x, pos$y, labels = round(pos$cor, 2),
col = ifelse(pos$cor != 1, "white", cols[201]), cex = .5, font = 1)
mtext(labels, side = 2, at = unique(pos$y) %>% sort(decreasing = T),
las = 1, adj = 1, cex = .75)
mtext(rev(labels), side = 3, at = unique(pos$y) %>% sort(decreasing = T),
las = 2, adj = 0, cex = .75)
mtext("A", side = 3, at = -3, cex = 1.15, line = 2.5)
# Create Cohen's d plot
ypos <- unique(pos$y) %>% sort(decreasing = TRUE)
cols <- rev(cividis(100))
plot.new()
plot.window(xlim = c(-.2, .8), ylim = range(ypos) + c(-.5, .5))
d_lines <- sapply(seq(-.2, .8, .1), function(x)
lines(c(x, x), range(ypos) + c(-.5, .5), lty = 2, lwd = .5)
)
lines(c(0, 0), range(ypos) + c(-.5, .5), lwd = 2)
for (i in 1:length(ypos)) lines(effects[c(1, 3), i], ypos[c(i, i)], lwd = 2)
points(effects[2, ], ypos, pch = 15, cex = 1.5,
col = cols[(effects[2, ] + .3) * 100 %>% round()])
labs <- sapply(seq(-.2, .8, .1) %>% round(1), function(x)
str_sub(x, nchar(x) - 1, nchar(x))
)
mtext(labs, at = seq(-.2, .8, .1), side = 3, cex = .8)
mtext(expression(paste("Cohen's ", italic(d))), side = 3, line = 2)
mtext(labels,
side = 2, at = unique(pos$y) %>% sort(decreasing = T),
las = 1, adj = 1, cex = .75, line = .5
)
mtext("B", side = 3, at = -.8, cex = 1.15, line = 2.5)
# Store plot for potential export
current_plot <- recordPlot()
# Curvature and complexity indices
cors[1:4, 5:7] %>% mean()
## [1] 0.5048213
# Curvature and temporal indices
cors[1:4, c(8, 10, 11)] %>% mean()
## [1] 0.2587851
# Complexity and temporal indices
cors[5:7, c(8, 10, 11)] %>% mean()
## [1] 0.3690757
# One factor
pca(measures[, -8], nfactors = 1)[["communality"]] %>% mean()
## [1] 0.5345219
# Five factors
pca(measures[, -8], nfactors = 5)[["communality"]] %>% mean()
## [1] 0.9216523
mt_data <- KH2017 %>%
mt_time_normalize() %>%
mt_length_normalize()
# Setup two pane plot layout
par(mfrow = c(2, 1))
# Heatmap of raw trajectories
mt_heatmap(
mt_data,
variable = mt_data$data$Condition == "Atypical",
smooth_radius = .5,
mean_image = .15,
mean_color = .15,
colors = c("white", cividis(7)[c(6, 1)]),
xres = 2000, bounds = c(-960, -100, 960, 1080),
verbose = FALSE
)
mtext("A", cex = 1.5, side = 3, at = 50, line = -1.5)
# Heatmap of smoothed differences
mt_diffmap(
mt_data,
condition = mt_data$data$Condition == "Typical",
colors = c(cividis(7)[6], "white", cividis(7)[1]),
xres = 1000, bounds = c(-960, -100, 960, 1080),
smooth_radius = 20, n_shades = 10,
verbose = FALSE
)
## creating heatmap: 1000 x 615 px
mtext("B", cex = 1.5, side = 3, at = 50, line = -1.5)
# Store plot for potential export
current_plot <- recordPlot()
# Preprocess trajectory data
mt_data <- KH2017 %>%
mt_length_normalize() %>%
mt_cluster(use = "ln_trajectories") %>%
mt_map(use = "ln_trajectories")
# Set colors
col <- custom_colors[1]
col2 <- "white"
# Setup three pane plot layout
layout(matrix(1:18, ncol = 3, byrow = TRUE), height = c(.2, rep(1, 5)))
# Prepare prototypes to display
prototypes <- mt_length_normalize(mt_prototypes, 20)
prototypes[, , 1] <-
prototypes[, , 1] * -mean(mt_data$ln_trajectories[, 20, "xpos"])
prototypes[, , 2] <-
prototypes[, , 2] * (mean(mt_data$ln_trajectories[, 20, "ypos"]) / 1.5)
# Compute percentages to display
tab1 <- table(mt_data$clustering$cluster)
tab1 <- round(tab1 / sum(tab1), 2) * 100
tab2 <- table(mt_data$prototyping$prototype)
tab2 <- round(tab2 / sum(tab2), 2) * 100
# Specify labels
txts <- c("Clustering", "Prototypes", "Prototype clustering")
txts2 <- c("A", "B", "C")
txts3 <- dimnames(mt_prototypes)[[1]]
# Plot column labels
# Note: setup of a figure of this size only works when opening graphics device
# of sufficient size (see commented out png call above)
for (i in 1:3) {
plot.new()
par(mar = c(0, 0, 0, 0))
plot.window(xlim = c(0, 1), c(0, .2))
text(.02, .1, txts2[i], font = 1, cex = 12)
}
# Plot trajectories per row
for (i in 1:5) {
# First column in row: Trajectories in cluster ----
# Extract clustered trajectories
current_traj <- mt_subset(mt_data, cluster == i, check = "clustering")
# Plot individual trajectories
mt_heatmap(
current_traj,
smooth_radius = 1,
colors = c("white", col),
bounds = c(-1000, -100, 1000, 1100),
xres = 1000,
verbose = FALSE
)
# Add aggregate trajectory
x <- colMeans(current_traj$ln_trajectories[, , "xpos"]) / 2 + 500
y <- (colMeans(current_traj$ln_trajectories[, , "ypos"]) + 100) / 2.05
lines(x, y, lwd = 55, col = col2)
points(x[c(1, length(x))], y[c(1, length(y))],
bg = col, col = col2, cex = 17, pch = 21, lwd = 10)
lines(x, y, lwd = 35, col = col)
# Add percentage labels
text(200, 120, paste0(tab1[i], "%"), cex = 10)
# Second column in row: Prototype ----
plot.new()
plot.window(xlim = c(-1000, 1000), ylim = c(-100, 1100))
points(prototypes[i, c(1, 20), 1], prototypes[i, c(1, 20), 2],
bg = col, col = col2, cex = 17, pch = 21, lwd = 7)
lines(prototypes[i, , 1], prototypes[i, , 2], lwd = 35, col = col)
x <- ifelse(i <= 3, ifelse(i == 1, 50, ifelse(i == 2, 300, -150)), 0)
text(x, 500, labels = txts3[i], col = "black", cex = 10)
# Third column in row: Trajectories mapped on prototype ----
# Extract trajectories mapped on prototype
current_traj <- mt_subset(mt_data, prototype == i, check = "prototyping")
# Plot individual trajectories
mt_heatmap(
current_traj,
smooth_radius = 1,
colors = c("white", col),
bounds = c(-1000, -100, 1000, 1100),
xres = 1000,
verbose = FALSE
)
# Add aggregate trajectory
x <- colMeans(current_traj$ln_trajectories[, , "xpos"]) / 2 + 500
y <- (colMeans(current_traj$ln_trajectories[, , "ypos"]) + 100) / 2.05
lines(x, y, lwd = 55, col = col2)
points(x[c(1, length(x))], y[c(1, length(y))],
bg = col, col = col2, cex = 17, pch = 21, lwd = 10)
lines(x, y, lwd = 35, col = col)
# Add percentage labels
text(200, 120, paste0(tab2[i], "%"), cex = 10)
}
# Store plot for potential export
current_plot <- recordPlot()
# Prototype frequencies per condition
prototype_frequencies <-
table(
mt_data$data$Condition,
mt_data$prototyping$prototype_label
)
prototype_frequencies
##
## straight curved cCoM dCoM dCoM2
## Atypical 165 38 37 56 24
## Typical 506 116 54 52 16
# Percentages
prototype_frequencies/
c(table(mt_data$data$Condition))
##
## straight curved cCoM dCoM dCoM2
## Atypical 0.51562500 0.11875000 0.11562500 0.17500000 0.07500000
## Typical 0.68010753 0.15591398 0.07258065 0.06989247 0.02150538
# Chi-squared test of prototype frequency
prototype_chisq <-
chisq.test(prototype_frequencies)
prototype_chisq
##
## Pearson's Chi-squared test
##
## data: prototype_frequencies
## X-squared = 57.968, df = 4, p-value = 7.748e-12
# Extract residuals
prototype_chisq$residuals
##
## straight curved cCoM dCoM dCoM2
## Atypical -2.5908103 -1.2219092 1.8410798 4.1266634 3.4510977
## Typical 1.6991203 0.8013597 -1.2074277 -2.7063725 -2.2633190
# Preprocess trajectory data
mt_data <- KH2017 %>%
mt_time_normalize() %>%
mt_angles(use = "tn_trajectories")
# Transform angle and position into long format
pos_angle_long <- mt_export_long(
mt_data,
use = "tn_trajectories",
use_variables = c("steps", "xpos", "angle_v"),
use2_variables = c("Condition", "subject_nr")
)
# Setup function that runs a mixed model per time step
mixed_model_per_step <- function(step, dv){
current_data <-
pos_angle_long %>%
filter(steps == step) %>%
filter(is.na(.data[[dv]])==FALSE)
# Do not run model if there is no non-NA data
if(nrow(current_data) == 0){
current_model <- "not_run"
current_p <- 1
current_n <- 0
# If there is data, count number of observations
} else{
current_desc <-
current_data %>%
group_by(Condition) %>%
summarize(
n = n(),
sd = sd(.data[[dv]]),
n_subjects = length(unique(subject_nr))
)
current_n <- sum(current_desc$n)
# Only run model if there is data for at least two participants and
# if SD of variable is > 0 in each condition
if((min(current_desc$n_subjects) > 2) & (min(current_desc$sd > 0))){
current_model <- mixed(
as.formula(paste(dv, "(1|subject_nr)+Condition", sep = "~")),
data = current_data,
progress = FALSE
)
current_p <- current_model$anova_table$`Pr(>F)`
current_model <- "run"
# Otherwise set p to 1
} else{
current_model <- "not_run"
current_p <- 1
}
}
return(
tibble(
steps = step,
p = current_p,
sig = p < .05,
n = current_n,
model = current_model
)
)
}
# Run mixed models for xpos and angle_v
mixed_models_xpos <-
unique(pos_angle_long$steps) %>%
map_dfr(mixed_model_per_step, dv = "xpos")
mixed_models_angle <-
unique(pos_angle_long$steps) %>%
map_dfr(mixed_model_per_step, dv = "angle_v")
# Retrieve significant differences
mixed_models_xpos$steps[mixed_models_xpos$sig]
## [1] 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
## [26] 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
mixed_models_angle$steps[mixed_models_angle$sig]
## [1] 10 38 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
## [20] 64 65 66 69 70 71 78 80 81 82 83 84 85 86 87 88 89 90 91
## [39] 92 93 94 95 96 97 98 100 101
# Create plot for x positions
p1 <- mt_plot(
mt_data,
use = "tn_trajectories",
x = "steps", y = "xpos",
color = "Condition", alpha = .1
) +
mt_plot_aggregate(
mt_data,
use = "tn_trajectories",
x = "steps", y = "xpos",
color = "Condition",
size = 2,
return_type = "geom"
) +
scale_color_manual(values = custom_colors_3[c(1, 3)]) +
labs(x = "Time step", y = "Position on horizontal axis (x)") +
geom_text(
label = "*", color = "black", size = 2,
mapping = aes(x = steps, y = 1000),
data = mixed_models_xpos %>% filter(sig)
)+
theme(legend.position = "none")
# Create plot for angles
p2 <- mt_plot(
mt_data,
use = "tn_trajectories",
x = "steps", y = "angle_v",
color = "Condition", alpha = .05
) +
mt_plot_aggregate(
mt_data,
use = "tn_trajectories",
x = "steps", y = "angle_v",
color = "Condition",
size = 2,
return_type = "geom",
.funs = ~ mean(.x, na.rm = TRUE)
) +
scale_color_manual(values = custom_colors_3[c(1, 3)]) +
scale_y_continuous(
breaks = seq(-pi, pi, pi / 2),
labels = scales::math_format(.x * pi, format = function(x) x / pi)
) +
labs(x = "Time step", y = "Angle relative to vertical axis (y)") +
geom_text(
label = "*", color = "black", size = 2,
mapping = aes(x = steps, y = pi),
data = mixed_models_angle %>% filter(sig)
)+
theme(legend.position = "top")
p1 + p2 + plot_annotation(tag_levels = "A")
# Preprocess trajectory data
mt_data <- KH2017 %>%
mt_length_normalize() %>%
mt_map(use = "ln_trajectories") %>%
mt_measures() %>%
# Resample trajectories to allow averaging non-normalized trajectories
mt_resample(step_size = 10, exact_last_timestamp = FALSE)
# Add data from other elements to data element
mt_data$data$RT <- mt_data$measures$RT
mt_data$data$prototype_label <- mt_data$prototyping$prototype_label
mt_aggregate(
mt_data,
use = "measures", use_variables = "RT",
use2_variables = "Condition",
.funs = "median"
)
rt_freq <- table(cut(mt_data$data$RT, breaks = c(0, 1500.5, 2500.5, 5000.5, Inf)))
rt_freq
##
## (0,1.5e+03] (1.5e+03,2.5e+03] (2.5e+03,5e+03] (5e+03,Inf]
## 420 469 142 33
rt_freq / nrow(mt_data$data)
##
## (0,1.5e+03] (1.5e+03,2.5e+03] (2.5e+03,5e+03] (5e+03,Inf]
## 0.39473684 0.44078947 0.13345865 0.03101504
p1 <- mt_plot(
mt_data,
use = "rs_trajectories", x = "timestamps", y = "xpos",
color = "Condition", alpha = .1,
subset = RT <= 1500
) +
scale_color_manual(values = cividis(3)[c(1, 3)]) +
labs(x = "Time in ms", y = "Position (x)", subtitle = "RT <= 1500") +
theme_minimal() +
theme(legend.position = "none",
plot.subtitle = element_text(size = 8, hjust = 0.5)) +
coord_cartesian(ylim = c(-800, 800)) +
mt_plot_aggregate(
mt_data,
use = "rs_trajectories", x = "timestamps", y = "xpos",
color = "Condition",
size = 2,
return_type = "geom",
subset = RT <= 1500
)
p2 <- mt_plot(
mt_data,
use = "rs_trajectories", x = "timestamps", y = "xpos",
color = "Condition", alpha = .1,
subset = RT > 1500 & RT <= 2500
) +
scale_color_manual(values = cividis(3)[c(1, 3)]) +
labs(x = "Time in ms", y = "Position (x)", subtitle = "1500 < RT <= 2500") +
theme_minimal() +
theme(legend.position = "none",
plot.subtitle = element_text(size = 8, hjust = 0.5)) +
coord_cartesian(ylim = c(-800, 800)) +
mt_plot_aggregate(
mt_data,
use = "rs_trajectories", x = "timestamps", y = "xpos",
color = "Condition",
size = 2,
return_type = "geom",
subset = RT > 1500 & RT <= 2500
) +
theme(axis.text.y = element_blank(), axis.title.y = element_blank())
p3 <- mt_plot(
mt_data,
use = "rs_trajectories", x = "timestamps", y = "xpos",
color = "Condition", alpha = .1,
subset = RT > 2500 & RT <= 5000
) +
scale_color_manual(values = cividis(3)[c(1, 3)]) +
labs(x = "Time in ms", y = "Position (x)", subtitle = "2500 < RT <= 5000") +
theme_minimal() +
theme(legend.position = "none",
plot.subtitle = element_text(size = 8, hjust = 0.5)) +
coord_cartesian(ylim = c(-800, 800)) +
mt_plot_aggregate(
mt_data,
use = "rs_trajectories", x = "timestamps", y = "xpos",
color = "Condition",
size = 2,
return_type = "geom",
subset = RT > 2500 & RT <= 5000
)+
theme(axis.text.y = element_blank(), axis.title.y = element_blank())
h <- ggplot(mt_data$data, aes(x = RT, fill = Condition, color = Condition)) +
geom_density(alpha = .55) +
guides(fill = guide_legend(override.aes = list(alpha=1))) +
theme_minimal() +
scale_fill_manual(values = cividis(3)[c(1, 3)]) +
scale_color_manual(values = cividis(3)[c(1, 3)]) +
xlim(0, 5000) +
theme(legend.position = "top") +
labs(x = "Response time in ms", y = "Density") +
theme(axis.text.y = element_blank()) +
theme(legend.text = element_text(size = 6),
legend.title = element_text(size = 8),
legend.key.size = unit(.8, "lines")) +
geom_vline(xintercept = c(1500, 2500), linetype = "dashed", size = .5)
h2 <- ggplot(
mt_data$data, aes(x = RT, fill = prototype_label, color = prototype_label)
) +
geom_density(alpha = .55) +
guides(fill = guide_legend(override.aes = list(alpha=1))) +
theme_minimal() +
scale_fill_manual(values = cividis(5), name = "Trajectory type") +
scale_color_manual(values = cividis(5), name = "Trajectory type") +
xlim(c(0, 5000)) +
theme(legend.position = "top") +
labs(x = "Response time in ms", y = "Density") +
theme(axis.text.y = element_blank()) +
theme(legend.text = element_text(size = 6),
legend.title = element_text(size = 8),
legend.key.size = unit(.8, "lines")) +
geom_vline(xintercept = c(1500, 2500), linetype = "dashed", size = .5)
h / (p1 + p2 + p3) / h2 + plot_annotation(tag_levels = "A")
## Create prototype mapping using standard trajectory preprocessing first
mt_data <- KH2017 %>%
mt_length_normalize() %>%
mt_map(use = "ln_trajectories")
mt_data$data$prototype_label <- mt_data$prototyping$prototype_label
## Exclude potential phase without movement at beginning and end of trial
mt_data <- mt_data %>%
mt_exclude_initiation(reset_timestamps = TRUE) %>%
mt_exclude_finish()
## Calculate derivatives and then time-normalize trajectories with
## dimensions set to all meaning that derivatives are also time-normalized
mt_data <- mt_data %>%
mt_derivatives() %>%
mt_time_normalize(dimensions = "all")
A similar function will be included in mousetrap package.
smooth <- function(x, pos, sd = 2) {
sm <- numeric(length(x))
for (i in 1:length(x)) {
w <- dnorm(pos, pos[i], sd = sd)
sm[i] <- sum(x * w, na.rm = T) / sum(w, na.rm = T)
}
sm
}
agg_traj <- mt_aggregate(
mt_data,
use = "tn_trajectories",
use2_variables = "prototype_label", trajectories_long = TRUE
) %>%
group_by(prototype_label) %>%
mutate(
sm_vel = smooth(vel, steps, sd = 3),
sm_acc = smooth(acc, steps, sd = 6)
) %>%
ungroup()
vel_plot <- ggplot(
agg_traj, aes(steps, sm_vel, col = prototype_label, fill = prototype_label)
) +
facet_wrap(~prototype_label, ncol = 5) +
geom_path(size = 2, show.legend = FALSE) +
scale_color_manual(values = cividis(5)) +
scale_fill_manual(values = cividis(5)) +
labs(
x = "Time step",
y = "Average velocity"
)
acc_plot <- ggplot(
agg_traj, aes(steps, sm_acc, col = prototype_label, fill = prototype_label)
) +
facet_wrap(~prototype_label, ncol = 5) +
geom_path(size = 2, show.legend = FALSE) +
scale_color_manual(values = cividis(5)) +
scale_fill_manual(values = cividis(5)) +
labs(
x = "Time step",
y = "Average acceleration"
)
vel_plot / acc_plot
# Setup folder in working directory where design factors data should be stored
dir.create("design_factors_data")
# Download design factors data using OSF file links and read them into R
# (if files already exist, download will be skipped and data will still be loaded)
design_factors_raw <-
c(
"1" = "https://osf.io/7vrkz/",
"2" = "https://osf.io/5hcju/",
"3" = "https://osf.io/7bfhz/"
) %>%
map_dfr(
~ osf_retrieve_file(.) %>%
osf_download(path = "design_factors_data", conflicts = "skip"),
.id = "experiment"
) %>%
pull(local_path, name = "experiment") %>%
map_dfr(read_csv, .id = "experiment")
# Filter and label design factors data
design_factors_raw <-
design_factors_raw %>%
mutate(
Typicality = factor(Condition, levels = c("Typical", "Atypical")),
Manipulation = str_to_title(group),
Manipulation = factor(
Manipulation,
levels = c(
"Click", "Touch", "Default", "Slow", "Static","Dynamic",
"Initmax", "Rtmax"
),
labels = c(
"Click", "Touch", "Default", "Slow", "Static","Dynamic",
"Timed: Initiation", "Timed: Response"
)
)
) %>%
filter(correct == 1)
# Import mouse-tracking data
mt_data <- mt_import_mousetrap(
design_factors_raw,
xpos_label = c("xpos_initial_phase", "xpos_get_response"),
ypos_label = c("ypos_initial_phase", "ypos_get_response"),
timestamps_label = c("timestamps_initial_phase", "timestamps_get_response")
)
# Preprocess mouse-tracking data
mt_data <- mt_data %>%
mt_remap_symmetric() %>%
mt_align_start() %>%
mt_time_normalize()
# Create plots per experiment
p1 <-
mt_data %>%
mt_subset(experiment == 3) %>%
mt_plot(
use = "tn_trajectories",
wrap_var = "Manipulation", wrap_ncol = 4,
alpha = .025
) +
coord_cartesian(xlim = c(-990, 990), ylim = c(-50, 970), expand = FALSE) +
labs(x = NULL, y = NULL) +
theme(axis.text.x = element_blank())
p2 <-
mt_data %>%
mt_subset(experiment == 1) %>%
mt_plot(
use = "tn_trajectories",
wrap_var = "Manipulation", wrap_ncol = 2,
alpha = .025
) +
coord_cartesian(xlim = c(-990, 990), ylim = c(-50, 970), expand = FALSE) +
labs(x = NULL, y = NULL)
p3 <-
mt_data %>%
mt_subset(experiment == 2) %>%
mt_plot(
use = "tn_trajectories",
wrap_var = "Manipulation", wrap_ncol = 2,
alpha = .025
) +
coord_cartesian(xlim = c(-990, 990), ylim = c(-50, 970), expand = FALSE) +
labs(x = NULL, y = NULL) +
theme(axis.text.y = element_blank())
((p1) / (p2 | p3)) +
plot_annotation(tag_levels = "A") &
theme(plot.tag = element_text(size = 10))