3 Illustrations of Task Design and Analysis Logic
3.1 Day Learning Task Design
Here is a description of the design of the Day Learning Task from the methods section:
In this task, 20 of the 21 scenes, which were shown in the picture viewing tasks, were presented repeatedly. This time, however, they were grouped into multiple sequences introduced to participants as “virtual days”. There were four different sequences, each comprising 5 events. Events from the same sequence were always shown in a specific order and with a specific time delay between them. Scenes were on screen for 1.5 s. At the end of each sequence, an image of a moon was shown for 5 s, then the next sequence began. Every sequence was presented 7 times. There were 7 mini-blocks in this task. Within each of these, every sequence was presented once. At the end of a mini-block, a 30-s break followed, then the next block started. The order in which the sequences were presented differed randomly across the 7 mini-blocks.
We instructed participants that the scenes depicted events from the life of a family and that the sequences of event images corresponded to different days in the family’s life. Participants were asked to memorize which events made up the different sequences (Figure 1C). We further instructed them to learn when during the respective sequence each event occurred. Specifically, we asked participants to learn event times relative to a virtual clock. This clock was running hidden from participants and event images were shown whenever the hidden clock reached the specific event time (Figure 1C, Supplemental Figure 2AB). The task was devised such that participants had to rely on their experience of passing real time and mnemonic construction to infer the times of events.
Specifically, to give participants an indication of virtual time, the hidden clock was made visible 6 times for every presentation of a sequence: once before the first event, once in between successive events, and once after the last event. Participants received no cues about elapsing real time, but had to use their experience of passing real time between virtual time cues to infer the event times relative to the hidden virtual clock. Importantly, the exposure of the hidden clock occurred at random times for each sequence presentation (Supplemental Figure 2CD), with the constraint that it could not be revealed closer than 2 s to a preceding or subsequent event. Thus, participants saw different time cues in each repetition of a sequence. For example, while a specific event always happened at the same virtual time, e.g. 2:07 p.m., the virtual clock could be exposed at any time before the event, e.g. corresponding to 1:32 p.m. in the first repetition of the sequence, and corresponding to 1:17 p.m. in the second repetition. Because true event times were never revealed, participants could not exclusively rely on associative learning to solve the task. Time cues were visible for 1.5 s, but displayed only the time at the start of exposure, i.e. the displayed time did not change within the duration of its presentation.
In short, participants had to combine their experience-based estimates of passing time with the time cues provided by the exposures of the otherwise hidden clock to infer the time at which each event in each sequence took place. Crucially, we varied the speed of the hidden clock between sequences in an effort to partly dissociate real time (in seconds) from virtual time (in virtual hours). Thus, for two sequences more virtual time passed in a comparable amount of real elapsing time (Figure 1C, Supplemental Figure 2). Correlations between the linearly increasing time metrics are inevitably high (Pearson correlation of virtual time with order r=0.969 and virtual time with real time r=0.975). Still this manipulation allowed us to determine using multiple regression whether virtual time explained constructed event times when competing for variance with real elapsed time and event order and whether hippocampal pattern similarity changes related to temporal distances in virtual time beyond ordinal distances and real time distances. Regression models including collinear predictor variables do not result in biased parameter estimates96,97.
Let’s illustrate some features of the design.
First we need to load the data from the design files. These contain the virtual time (in virtual hours) for each event of each day and the scanner time (in seconds) since the time of the day start.
<- rep(1:n_days, each = n_events_day)
day_id <- tibble(day_id)
design_tbl
# load design file with virtual time
<- here("data", "behavior", "design", "daysTime.txt")
fname <- read.csv(fname, header = FALSE)
csv_txt $virtual_time <- c(t(as.matrix(csv_txt)))
design_tbl
# load design file with event times in seconds (after day start), i.e. real time
# this is the scanner time
<- here("data", "behavior", "design", "daysTimeS.txt")
fname <- read.csv(fname, header = FALSE)
csv_txt $scanner_time <- c(t(as.matrix(csv_txt)))
design_tbl
# scanner time relative to first image
$rel_scanner_time <- c(
design_tbl$scanner_time[1:5] - design_tbl$scanner_time[1],
design_tbl$scanner_time[6:10] - design_tbl$scanner_time[6],
design_tbl$scanner_time[11:15] - design_tbl$scanner_time[11],
design_tbl$scanner_time[16:20] - design_tbl$scanner_time[16]) design_tbl
Here are some plots to show some features of the design.
Events in virtual and real time
This plot shows an overview of the design. We have 4 virtual days with 5 events each. The times of these events are shown based on different metrics:
- The scanner time since the presentation of the first day of an event (global x-axis)
- The virtual hours of each days (separate x-axis for each row)
In combination, you can see that across days different amounts of virtual time pass for a given amount of scanner time.
# time labels in virtual hours rather than decimals
$virtual_hours <- sprintf("%02.0f:%02.0f",
design_tblfloor(design_tbl$virtual_time),
60*(design_tbl$virtual_time-floor(design_tbl$virtual_time)))
# create a new dataframe to use for plotting virtual time axes
<- tibble(x = c(rep(-5,4), rep(65,4)))
virt_h_df $y <- rep(1:4,2)
virt_h_df
# create a new dataframe to use for plotting virtual time axes tick marks
<- tibble(x = rep(design_tbl$rel_scanner_time, each=2))
tick_df $y <- c(0.75, 0.8) + rep(design_tbl$day_id, each=2) -1
tick_df$grp <- rep(1:20, each=2)
tick_df$day <- rep(1:4, each=10)
tick_df
# initialize the plot
ggplot(design_tbl, aes(x = rel_scanner_time, y = day_id, group = day_id)) +
# plot line for each day
geom_line(size = 1, colour = "darkgrey") +
# add points for the data
geom_point(aes(fill = virtual_time), colour = "darkgrey",
size = 2, stroke = 0.5, shape = 21) +
# plot line for each day that will become virtual time axis
geom_line(data = tick_df, aes(x = x, y = day-0.2, group = day),
size = 0.5, colour = "black") +
# extend the axes beyond the events
geom_line(data = virt_h_df, aes(x = x, y = y-0.2, group = y),
size = 0.5, colour = "black", linetype = 3) +
# add tick marks
geom_line(data = tick_df, aes(x = x, y = y, group = grp),
size = 0.5, colour = "black") +
# add labels with virtual hours
geom_label(aes(x = rel_scanner_time, y = day_id-0.45), size=6*0.352777778,
label = design_tbl$virtual_hours, fill = "#D1D3D4", label.size = 0,
label.padding=unit(0.5, "mm"), family=font2use) +
# set the x- and y-axis range and ticks
scale_x_continuous(limits = c(-7,68.5), breaks=seq(0,65,10)) +
scale_y_continuous(limits = c(0.3, 4.1), breaks=seq(1,4,1)) +
::scale_fill_scico(begin = 0.1, end = 0.7, palette = "devon") +
scico# set plot title, legend and axis labels
labs(x = "seconds since first event", y = "day") +
# make it pretty
theme_cowplot() +
theme(legend.key = element_rect(fill = "transparent", colour = "transparent"),
text = element_text(size=10, family = font2use),
axis.text = element_text(size=8),
legend.text=element_text(size=8),
legend.title=element_text(size=8),
axis.ticks = element_line(colour = "black", size = 0.5),
axis.line = element_line(colour = 'black', size = 0.5),
legend.position = "none")
Events in virtual time
Two of the virtual days were short, ranging from 8am to 8pm, and two of the days were long, lasting from 6am until midnight. This can easily be seen if we plot the events in virtual time.
# initialize the plot
<- ggplot(design_tbl, aes(x = virtual_time, y = day_id, group = day_id, fill = virtual_time)) +
sfiga # plot line for each day
geom_line(size = 1, colour = ultimate_gray) +
# add points for the data
geom_point(aes(fill = virtual_time), colour = ultimate_gray,
size = 2, stroke = 0.5, shape = 21) +
# change color
::scale_fill_scico(begin = 0.1, end = 0.7, palette = "devon") +
scico# set the x-axis range and ticks
scale_x_continuous(limits = c(6,24), breaks=seq(6,24,3)) +
scale_y_continuous(limits = c(0.5, 4.2)) +
# set plot title, legend and axis labels
labs(x = "virtual time", y = "day") +
# make it pretty
theme_cowplot() +
theme(legend.key = element_rect(fill = "transparent", colour = "transparent"))
sfiga
Events in real time
In an effort to dissociate real time (in seconds) from virtual time (in virtual hours), both the long and short days were presented within the same actual time span, i.e. around 60 seconds from the first to the last event. In effect, the hidden clock was running faster for the long days than for the short days.
If we plot the scanner time relative to the presentation of the first event picture of a given day it becomes obvious that for two pairs of days the respective scanner time elapsing between events is almost identical.
# initialize the plot
<- ggplot(design_tbl, aes(x = rel_scanner_time, y = day_id, group = day_id, fill = virtual_time)) +
sfigb # plot line for each day
geom_line(size = 1, colour = ultimate_gray) +
# add points for the data
geom_point(aes(fill = virtual_time), colour = ultimate_gray,
size = 2, stroke = 0.5, shape = 21) +
# change color
::scale_fill_scico(begin = 0.1, end = 0.7, palette = "devon") +
scico# set the x-axis range and ticks
scale_x_continuous(limits = c(-10,70), breaks=seq(-10,70,10)) +
scale_y_continuous(limits = c(0.5, 4.2)) +
# set plot title, legend and axis labels
labs(x = "seconds since first event", y = "day") +
# make it pretty
theme_cowplot() +
theme(legend.key = element_rect(fill = "transparent", colour = "transparent"))
sfigb
Correlations of virtual time with order and real time
Correlation of virtual time and order: Pearson r=0.969
Correlation of virtual time and real time: Pearson r=0.975
Correlation of order and real time: Pearson r=0.991
Time cues in virtual and real time
Let’s further illustrate the design for one randomly chosen example subject by plotting the time cues. The virtual times of events were identical for all participants, but the times cues differed between participants.
Prepare the data by loading the logfile from the day learning task and creating a long design table.
set.seed(7)
<- sample(subjects,1)
i_sub
# create a long version of the design table by repeating each row 7 times,
# i.e. once for each repetition
<- design_tbl %>%
design_tbl_long ::slice(rep(1:n(), each = 7)) %>%
dplyrmutate(rep = rep(c(1:7), n_days*n_events_day),
day_id = factor(sprintf("day %d", day_id), levels = c("day 4", "day 3", "day 2", "day 1")))
# load the logfile and store as tibble
<- file.path(dirs$dlt_log_dir, sprintf("P%s-virtemdata.txt", i_sub))
fn <- read.table(fn)
dlt_log colnames(dlt_log) <- c('fc_on', 'fc_off', 'cue_start', 'cue_id', 'cue_end',
'vol', 'rep', 'real_time', 'day', 'trial_endtime')
<- as_tibble(dlt_log)
dlt_log
# save the time cue log entries for each day and repetition
<- dlt_log %>%
timecue_log group_by(day, rep) %>% # log entries for each of day repetition
mutate(rel_scanner_time = real_time -real_time[2]) %>% # add real time relative to first event
::slice(seq(1,11,2)) %>% # keep only the odd entries, which are the time cues
dplyrungroup() %>%
mutate(
virtual_time_dec = floor(cue_id)+((cue_id-floor(cue_id))/.6), # virtual time in decimals as in design table
day_id = factor(sprintf("day %d", day), levels = c("day 4", "day 3", "day 2", "day 1"))
)
Then let’s plot the events and time cues relative to virtual time, separately for the different repetitions of each day.
# virtual time
<- ggplot(timecue_log, aes(x=virtual_time_dec, y=rep, group = rep)) +
sfigc geom_line(size = 1, colour = ultimate_gray) +
geom_point(shape=18, color="black", size=2) +
geom_point(data=design_tbl_long, aes(x=virtual_time, y=rep, fill = virtual_time),
size = 2, color = ultimate_gray, stroke = 0.5, shape = 21) +
::scale_fill_scico(begin = 0.1, end = 0.7, palette = "devon") +
scicofacet_wrap(~day_id, ncol=1) +
scale_x_continuous(limits = c(6,24), breaks=seq(6,24,3)) +
scale_y_continuous(limits = c(0,8), breaks=seq(1,7,1)) +
labs(x = "virtual time", y = "repetition") +
theme_cowplot() +
theme(strip.background = element_blank(), #strip.text = element_blank(),
legend.position = "none")
sfigc
Lastly, let’s plot the events and time cues in real time relative to the first event of a given day, separately for the different repetitions of each day.
# real time relative to first event of the day
<- ggplot(timecue_log, aes(x=rel_scanner_time, y=rep, group = rep)) +
sfigd geom_line(size = 1, color = "darkgrey") +
geom_point(shape=18, color="black", size=2) +
geom_point(data=design_tbl_long, aes(x=rel_scanner_time, y=rep, fill = virtual_time),
size = 2, color = "darkgrey", stroke = 0.5, shape = 21) +
::scale_fill_scico(begin = 0.1, end = 0.7, palette = "devon") +
scicofacet_wrap(~day_id, ncol=1) +
scale_y_continuous(limits = c(0,8), breaks=seq(1,7,1)) +
scale_x_continuous(limits = c(-10,70), breaks=seq(-10,70,10)) +
labs(x = "seconds since first event", y = "repetition") +
theme_cowplot() +
theme(strip.background = element_blank(), #strip.text = element_blank(),
legend.position = "none")
sfigd
Supplemental figure for task design
Now we are ready to compose the figure, which will go to the supplement.
= "
layout AB
CD
CD
CD
CD
CD"
<- sfiga + sfigb + sfigc + sfigd +
sfig plot_layout(design = layout, guides = "collect") &
theme(text = element_text(size=10, family=font2use),
axis.text = element_text(size=8),
legend.text=element_text(size=8),
legend.title=element_text(size=8),
legend.title.align = 0,
legend.position = "right",
legend.justification = "bottom",
legend.key.size = unit(3,"mm")) &
guides(fill = guide_colorbar(title.position = "top",
title.hjust = 0.5)) &
labs(fill = "virtual\ntime") &
plot_annotation(theme = theme(plot.margin = margin(t=0, r=-5, b=-8, l=-5, unit="pt")),
tag_levels = 'A')
# save as png and pdf and print to screen
<- here("figures", "sf02")
fn ggsave(paste0(fn, ".pdf"), plot=sfig, units = "cm",
width = 17.4, height = 17, dpi = "retina", device = cairo_pdf)
ggsave(paste0(fn, ".png"), plot=sfig, units = "cm",
width = 17.4, height = 17, dpi = "retina", device = "png")
3.2 Representational Similarity Analysis Logic
To illustrate the logic of the RSA on representational change, let’s create some matrices that will be used in the analysis logic figure of the manuscript.
# some general params
<- rep(1:4, each=5)
day <- rep(1:5, 4) event
We start with a random matrix of similarity values in the pre-learning scan. There should be no pattern.
# CREATE AND PLOT NOISE MATRIX FOR PRE-SIMILARITY
<- matrix(runif(400, -0.8, 0.8), n_days*n_events_day, n_days*n_events_day)
pre_sim_mat
# make data frame from matrix and add variables for plotting
lower.tri(pre_sim_mat, diag=TRUE)]<-NA
pre_sim_mat[<- reshape2::melt(pre_sim_mat) %>%
pre_sim_df filter(!is.na(value)) %>%
mutate(
day1 = day[Var1],
day2 = day[Var2],
day_comb = sprintf("%d_%d", day1, day2),
same_day = day1==day2)
# plot
<- ggplot(data = pre_sim_df, aes(x=Var1, y=Var2, fill=value)) +
fig_pre_sim geom_tile()+
scale_fill_gradient(low = "white", high="black", na.value = NA,
breaks = range(pre_sim_df$value), labels=c("low", "high")) +
coord_flip(xlim = c(0.5,19.5), ylim = c(20.5,0.5),
expand = FALSE) +
guides(fill=guide_colorbar(title = "similarity", title.position = "top",
title.hjust = 0.5)) +
theme(aspect.ratio = 1,
axis.title=element_blank(),
axis.text=element_blank(),
axis.ticks=element_blank(),
panel.background = element_blank(),
panel.border = element_blank(),
panel.grid = element_blank(),
legend.position = "none",
plot.margin=unit(c(0,0,0,0), units = "cm"))
# save and print
<- here("figures", "f3_pre_sim")
fn ggsave(paste0(fn, ".pdf"), plot=fig_pre_sim, units = "cm",
width = 2, height = 2, dpi = "retina", device = cairo_pdf)
fig_pre_sim
To plot the RSA prediction matrix, we generate a matrix that has the pairwise temporal distances between events in its cell. We also generate a similarity matrix that mirrors our effects in the hippocampus, e.g. where similarity increases with distance within a sequence and decreases with distance between sequences.
# CREATE DISTANCE AND IDEALIZED SIMILARITY CHANGE MATRICES
# load design file with virtual time
<- here("data", "behavior", "design", "daysTime.txt")
fname <- read_csv(fname, col_names = FALSE, col_types = "ddddd")
virtual_time <- c(t(as.matrix(virtual_time)))
virtual_time
<- matrix(NA, n_days*n_events_day, n_days*n_events_day)
dist_mat <- matrix(NA, n_days*n_events_day, n_days*n_events_day)
change_sim_mat # loop over all pairs of events
for (i1 in 1:20){
for (i2 in 1:20){
# temporal distance for this pair
<- abs(virtual_time[i2]-virtual_time[i1])
dist_mat[i1,i2]
# similarity depends on whether we are in the same day or not
if(day[i1]==day[i2]){
<- abs(virtual_time[i2]-virtual_time[i1])}
change_sim_mat[i1,i2] else {
<- range(virtual_time)[2]-range(virtual_time)[1]-abs(virtual_time[i2]-virtual_time[i1])}
change_sim_mat[i1,i2]
}
}
# PLOT DISTANCE MATRIX
# remove lower triangle from distance matrix
lower.tri(dist_mat, diag=TRUE)]<-NA
dist_mat[
# make data frame from matrix and add variables for plotting
<- reshape2::melt(dist_mat) %>%
dist_df filter(!is.na(value)) %>%
mutate(
day1 = day[Var1],
day2 = day[Var2],
day_comb = sprintf("%d_%d", day1, day2),
same_day = day1==day2)
# plot distance matrix
<- ggplot(data = dist_df, aes(x=Var1, y=Var2, fill=value)) +
fig_dist_mat geom_tile()+
scale_fill_gradient(low = "white", high="black", na.value = NA,
breaks = c(0,4), labels=c("low", "high")) +
coord_flip(xlim = c(0.5,19.5), ylim = c(20.5,0.5),
expand = FALSE) +
guides(fill=guide_colorbar(title = "temporal distance", title.position = "top",
title.hjust = 0.5)) +
theme(aspect.ratio = 1,
axis.title=element_blank(),
axis.text=element_blank(),
axis.ticks=element_blank(),
panel.background = element_blank(),
panel.border = element_blank(),
panel.grid = element_blank(),
legend.position = "none",
plot.margin=unit(c(0,0,0,0), units = "cm")) +
::geom_mark_rect(aes(group = day_comb, filter = same_day==FALSE),
ggforceexpand = 0.018, radius = 0,
color=unname(aHPC_colors["across_main"]), fill=NA, size=0.5)+
::geom_mark_rect(aes(group = day_comb, filter = same_day==TRUE),
ggforceexpand = 0.018, radius = 0,
color=unname(aHPC_colors["within_main"]), fill=NA, size=0.5)
# save and print
<- here("figures", "f3_dist_mat")
fn ggsave(paste0(fn, ".pdf"), plot=fig_dist_mat, units = "cm",
width = 2, height = 2, dpi = "retina", device = cairo_pdf)
fig_dist_mat
# PLOT SIMILARITY CHANGE MATRIX
# add noise to change similarity matrix (uniform distribution)
<- change_sim_mat+ matrix(runif(400, -0.4, 0.4), dim(change_sim_mat)[1])
change_sim_mat
# remove lower triangle from similarity change matrix
lower.tri(change_sim_mat, diag=TRUE)]<-NA
change_sim_mat[
# make data frame from matrix and add variables for plotting
<- reshape2::melt(change_sim_mat) %>%
change_sim_df filter(!is.na(value)) %>%
mutate(
day1 = day[Var1],
day2 = day[Var2],
day_comb = sprintf("%d_%d", day1, day2),
same_day = day1==day2)
# plot
<- ggplot(data = change_sim_df, aes(x=Var1, y=Var2, fill=value)) +
fig_sim_change geom_tile()+
scale_fill_gradient(low = "white", high="black", na.value = NA,
breaks = range(change_sim_df$value), labels=c("low", "high")) +
coord_flip(xlim = c(0.5,19.5), ylim = c(20.5,0.5),
expand = FALSE) +
guides(fill=guide_colorbar(title = "similarity", title.position = "top",
title.hjust = 0.5)) +
theme(aspect.ratio = 1,
axis.title=element_blank(),
axis.text=element_blank(),
axis.ticks=element_blank(),
panel.background = element_blank(),
panel.border = element_blank(),
panel.grid = element_blank(),
legend.position = "none",
plot.margin=unit(c(0,0,0,0), units = "cm")) +
::geom_mark_rect(aes(group = day_comb, filter = same_day==FALSE),
ggforceexpand = 0.018, radius = 0,
color=unname(aHPC_colors["across_main"]), fill=NA, size=0.5)+
::geom_mark_rect(aes(group = day_comb, filter = same_day==TRUE),
ggforceexpand = 0.018, radius = 0,
color=unname(aHPC_colors["within_main"]), fill=NA, size=0.5)
# save and print
<- here("figures", "f3_sim_change")
fn ggsave(paste0(fn, ".pdf"), plot=fig_sim_change, units = "cm",
width = 2, height = 2, dpi = "retina", device = cairo_pdf)
fig_sim_change
Lastly, we create a post-similarity matrix, which is the sum of the simulated pre-learning and the similarity change matrix.
# CREATE AND PLOT POST SIMILARITY MATRIX
# post similarity is sum of pre similarity and change similarity
<- pre_sim_mat + change_sim_mat
post_sim_mat
# remove lower triangle from similarity change matrix
lower.tri(post_sim_mat, diag=TRUE)]<-NA
post_sim_mat[
# make data frame from matrix and add variables for plotting
<- reshape2::melt(post_sim_mat) %>%
post_sim_df filter(!is.na(value)) %>%
mutate(
day1 = day[Var1],
day2 = day[Var2],
day_comb = sprintf("%d_%d", day1, day2),
same_day = day1==day2)
# plot
<- ggplot(data = post_sim_df, aes(x=Var1, y=Var2, fill=value)) +
fig_post_sim geom_tile()+
scale_fill_gradient(low = "white", high="black", na.value = NA,
breaks = range(post_sim_df$value), labels=c("low", "high")) +
coord_flip(xlim = c(0.5,19.5), ylim = c(20.5,0.5),
expand = FALSE) +
guides(fill=guide_colorbar(title = "similarity", title.position = "top",
title.hjust = 0.5)) +
theme(aspect.ratio = 1,
axis.title=element_blank(),
axis.text=element_blank(),
axis.ticks=element_blank(),
panel.background = element_blank(),
panel.border = element_blank(),
panel.grid = element_blank(),
legend.position = "none",
plot.margin=unit(c(0,0,0,0), units = "cm"))
# save
<- here("figures", "f3_post_sim")
fn ggsave(paste0(fn, ".pdf"), plot=fig_post_sim, units = "cm",
width = 2, height = 2, dpi = "retina", device = cairo_pdf)
fig_post_sim