Potential plots for CCN 2025 poster

Published

August 6, 2025

── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.2     ✔ tibble    3.2.1
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.0.4     
── 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
here() starts at /Users/visuallearninglab/Documents/visvocab

Note: The package "relayer" is highly experimental. Use at your own risk.

Loading required package: viridisLite

Loading required package: lme4

Loading required package: Matrix


Attaching package: 'Matrix'


The following objects are masked from 'package:tidyr':

    expand, pack, unpack



Attaching package: 'lmerTest'


The following object is masked from 'package:lme4':

    lmer


The following object is masked from 'package:stats':

    step



Attaching package: 'cowplot'


The following object is masked from 'package:lubridate':

    stamp



Attaching package: 'scales'


The following object is masked from 'package:viridis':

    viridis_pal


The following object is masked from 'package:purrr':

    discard


The following object is masked from 'package:readr':

    col_factor



Attaching package: 'rlang'


The following objects are masked from 'package:purrr':

    %@%, flatten, flatten_chr, flatten_dbl, flatten_int, flatten_lgl,
    flatten_raw, invoke, splice


Registering fonts with R
PROCESSED_DATA_PATH = here("data","main","processed_data")
trial_summary_data <- read.csv(here(PROCESSED_DATA_PATH, "level-trials_data.csv"))
trial_metadata <- read.csv(here("data", "metadata", "level-trialtype_data.csv"))
cvcl_similarities <- read.csv(here("data", "embeddings", "similarities-cvcl_data.csv"))
openclip_similarities <- read.csv(here("data", "embeddings", "similarities-openclip_data.csv"))
saycamvit_similarities <- read.csv(here("data", "embeddings", "similarities-saycamvit_data.csv"))
imagenetvit_similarities <- read.csv(here("data", "embeddings", "similarities-imagenetvit_data.csv"))
target_looking_trial_level <- read.csv(here("data", PROJECT_VERSION, "processed_data", "level-trialType_added-accuracy_data.csv"))
looking_data_summarized <- trial_summary_data |>
  filter(trial_exclusion == 0 & exclude_participant == 0 & exclude_participant_insufficient_data == 0) |>
  left_join(trial_metadata) |>
  arrange(AoA_Est_target)
Joining with `by = join_by(Trials.trialID, Trials.targetImage,
Trials.distractorImage, Trials.imagePair)`

openclip checkpoint number fix

## comparisons for openclip
total_checkpoints <- 256
num_selections <- 40

# Step 1: Log-spaced values in base 2
log_scale_checkpoints <- 2 ^ seq(log2(1), log2(total_checkpoints), length.out = num_selections)

# Step 2: Round to nearest integers
log_scale_checkpoints <- round(log_scale_checkpoints)

# Step 3: Remove duplicates and sort
unique_checkpoints <- sort(unique(log_scale_checkpoints))

helpers

fit_image_model <- function(summary_data) {
  lmer(
    scale(corrected_target_looking) ~ 
      scale(image_similarity) * scale(age_in_months) + 
      scale(AoA_Est_target) + 
      scale(MeanSaliencyDiff) +
      (1 | SubjectInfo.subjID) +
      (1 | Trials.targetImage),
    data = summary_data |> 
      mutate(age_in_months = SubjectInfo.testAge / 30)
  )
}

fit_text_model <- function(summary_data) {
  lmer(
    scale(corrected_target_looking) ~ 
      scale(text_similarity) * scale(age_in_months) + 
      scale(AoA_Est_target) + 
      scale(MeanSaliencyDiff) +
      (1 | SubjectInfo.subjID) +
      (1 | Trials.targetImage),
    data = summary_data |> 
      mutate(age_in_months = SubjectInfo.testAge / 30)
  )
}

CLIP plot

multiple_similarity_effects_plot <- function(data, x_var, y_var = "mean_value", group_var, input_title, prod = FALSE) {
  sim_type <- strsplit(x_var, "_")[[1]][1]
  
  # Conditionally filter data if prod is TRUE
  if (prod) {
    label_data <- data %>% 
      filter(Trials.targetImage == "acorn" | Trials.distractorImage == "acorn") %>%
      mutate(label = paste("Target:", Trials.targetImage, "\nDistractor:", Trials.distractorImage))
  } else {
    label_data <- data %>%
      mutate(label = paste("Target:", Trials.targetImage, "\nDistractor:", Trials.distractorImage))
  }

  # Build the plot
  p <- ggplot(data, aes(x = .data[[x_var]], y = .data[[y_var]], color = .data[[group_var]])) +
    geom_hline(yintercept = 0, linetype = "dashed", size=1.5) +
    geom_point(size = 8, alpha = 0.8) +
    geom_smooth(alpha = 0.3, size = 0, method = "lm", show.legend = FALSE) +
    stat_smooth(geom = "line", alpha = 0.8, size = 1.5, method = "lm", show.legend = FALSE) +
    coord_cartesian(ylim = c(-0.12, 0.22)) +
    geom_label_repel(
        color = "black",          # force label text to be black
      fill = "white",           # optional: white label background
    segment.color = "black",  # force label line to be black
      data = label_data,
      aes(label = label),
      segment.alpha = 0.7,
      nudge_y = ifelse(label_data$Trials.targetImage == "bulldozer", -0.02, 0.02),
      force = 10,
      force_pull = 0.1,
      size = 5,
      segment.size = 1.2,
      point.padding = unit(1, "lines"),
      min.segment.length = 0,
      box.padding = unit(0.5, "lines"),
      max.overlaps = Inf,
      label.padding = unit(0.25, "lines"),
      label.r = unit(0.5, "lines"),
      show.legend = FALSE
    ) +
    ylab("Baseline-corrected\nproportion target looking") +
    xlab("Target-distractor embedding similarity") +
    scale_y_continuous(breaks = seq(-0.1, 0.2, by = 0.1)) +
    scale_color_manual(values = c(
      "image_similarity" = "#215D89",  # blue
      "text_similarity" = "#B8481C"    # orange
    )) +
    guides(color = "none") +  # Remove legend (optional)
    theme(
      text = element_text(size = 16, face = "bold"),
      axis.title.x = element_text(
        face = "bold", 
        size = 29,
        margin = margin(t = 15, r = 0, b = 0, l = 0)
      ),
      legend.key = element_blank(),
      axis.title.y = element_text(
        face = "bold", 
        size = 29,
        margin = margin(t = 0, r = 10, b = 0, l = 0)
      ),
      axis.text = element_text(size = 24, face = "bold"),
      legend.title = element_blank(),
      legend.text = element_text(size = 22, face = "bold"),
      legend.position = "bottom",
      strip.text = element_text(size = 28, face = "bold"),
      strip.background = element_rect(fill = "gray90", color = NA),
      strip.text.x = element_text(margin = margin(t = 8, b = 8)),
      panel.spacing = unit(0.5, "cm")
    ) +
    facet_wrap(
      facets = ~ .data[[group_var]],
      dir = "v",
      strip.position = "top",
      labeller = as_labeller(c(
        "image_similarity" = "Image Similarity",
        "text_similarity" = "Text Similarity"
      )),
      ncol = 1,
      scales = "free"
    )

  # Conditionally add custom x-axis scales
  if (prod) {
    p <- p + facetted_pos_scales(
      x = list(
        image_similarity = scale_x_continuous(
          breaks = seq(0.5, 0.9, by = 0.1),
          limits = c(0.43, 0.85)
        ),
        text_similarity = scale_x_continuous(
          breaks = seq(0.7, 0.9, by = 0.05),
          limits = c(0.7, 0.91)
        )
      )
    )
  }

  return(p)
}
clip_data_summarized <- summarize_similarity_data(looking_data_summarized)
clip_data_long <- clip_data_summarized |>
  pivot_longer(cols = c("text_similarity", "image_similarity"), names_to = "sim_type", values_to = "similarity")
all_clip_plot <- multiple_similarity_effects_plot(clip_data_long, "similarity", group_var="sim_type", input_title="Looking time and CLIP embedding correlations", prod=TRUE)
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.
all_clip_plot
Warning: `is.ggproto()` was deprecated in ggplot2 3.5.2.
ℹ Please use `is_ggproto()` instead.
`geom_smooth()` using formula = 'y ~ x'
`geom_smooth()` using formula = 'y ~ x'

ggsave(here("figures",PROJECT_VERSION,"clip_plot_poster.svg"),all_clip_plot,width=7.5, height=10,bg = "white",device="pdf")
`geom_smooth()` using formula = 'y ~ x'
`geom_smooth()` using formula = 'y ~ x'

Saliency plots

# Prepare the data by combining both datasets with a grouping variable
trialid_level_summary <- target_looking_trial_level |> left_join(trial_metadata)
Joining with `by = join_by(Trials.trialID, AoA_Est_distractor, AoA_Est_target,
Trials.targetImage, Trials.distractorImage, Trials.imagePair)`
looking_data_first_appearance <- looking_data_summarized |>
  group_by(SubjectInfo.subjID, Trials.targetImage) |>
  arrange(Trials.ordinal, .by_group = TRUE) |>
  slice(1) |>
  ungroup()

baseline_looking_trial_level <- summarized_data(looking_data_first_appearance, "Trials.trialID", "mean_target_looking_baseline_window", c("Trials.trialID")) |> left_join(trial_metadata) 
Joining with `by = join_by(Trials.trialID)`
trialid_level_summary_firstinstance <- summarized_data(looking_data_first_appearance, "Trials.trialID", "corrected_target_looking", c("Trials.trialID")) |> left_join(trial_metadata) 
Joining with `by = join_by(Trials.trialID)`
trialid_level_summary_firstinstance$plot_type <- "Baseline-corrected"
baseline_looking_trial_level$plot_type <- "Baseline window"

combined_data <- bind_rows(trialid_level_summary_firstinstance, baseline_looking_trial_level)

# Create labels for trials where target is acorn and distractor is coconut
combined_data$label_text <- ifelse(
  combined_data$Trials.targetImage == "acorn" & combined_data$Trials.distractorImage == "coconut",
  paste("Target:", combined_data$Trials.targetImage, "\nDistractor:", combined_data$Trials.distractorImage),
  ""
)

# Create the combined faceted plot
combined_saliency_plot <- ggplot(combined_data, aes(x = MeanSaliencyDiff, y = mean_value)) +
  geom_smooth(alpha = 0.3, size = 0, method = "lm", show.legend = F, color = "#182B49") +  # Set color outside aes()
  stat_smooth(geom = "line", alpha = 0.8, size = 1.5, method = "lm", show.legend = F, color = "#182B49") +
  geom_point(size = 8, alpha = 0.8, color = "#182B49") +
   geom_label_repel(
        color = "black",          # force label text to be black
      fill = "white",           # optional: white label background
    segment.color = "black",  # force label line to be black
      aes(label = label_text),
      segment.alpha = 0.7,
      nudge_y = 0.04,
      force = 10,
      force_pull = 0.1,
      size = 5,
      segment.size = 1.2,
      point.padding = unit(1, "lines"),
      min.segment.length = 0,
      box.padding = unit(0.5, "lines"),
      max.overlaps = Inf,
      label.padding = unit(0.25, "lines"),
      label.r = unit(0.5, "lines"),
      show.legend = FALSE
    ) +
  
    geom_hline(data = data.frame(plot_type = "Baseline window", yint = 0.5), 
             aes(yintercept = yint), size=1.5, linetype = "dashed", color = "gray40", inherit.aes = FALSE) +
  # Dashed line at 0 for 'Baseline-corrected' facet
  geom_hline(data = data.frame(plot_type = "Baseline-corrected", yint = 0), 
             aes(yintercept = yint), linetype = "dashed", size=1.5, color = "gray40", inherit.aes = FALSE) +
  facet_wrap(~ plot_type, scales = "free_y", ncol = 2) +
  
  xlab("Target-distractor GBVS mean saliency difference") +
  ylab("Prop. target looking") +
  theme(
    text = element_text(size = 16, face = "bold"),
    axis.title.x = element_text(
      face = "bold", 
      size = 30,
      margin = margin(t = 15, r = 0, b = 0, l = 0)
    ),
    legend.key = element_blank(),
    axis.title.y = element_text(
      face = "bold", 
      size = 30,
      margin = margin(t = 0, r = 10, b = 0, l = 0)
    ),
    axis.text = element_text(size = 26, face = "bold"),
    legend.title = element_text(size = 22, face = "bold"),
    legend.text = element_text(size = 22, face = "bold"),
    legend.position = "bottom",
    strip.text = element_text(size = 28, face = "bold"),
    strip.background = element_rect(fill = "gray90", color = NA),
    strip.text.x = element_text(margin = margin(t = 8, b = 8)),
    panel.spacing = unit(0.5, "cm")
  )
Warning in geom_hline(data = data.frame(plot_type = "Baseline window", yint =
0.5), : Ignoring unknown parameters: `inherit.aes`
Warning in geom_hline(data = data.frame(plot_type = "Baseline-corrected", :
Ignoring unknown parameters: `inherit.aes`
# Display the plot
combined_saliency_plot
`geom_smooth()` using formula = 'y ~ x'
`geom_smooth()` using formula = 'y ~ x'

# Save the combined plot
ggsave(here("figures", PROJECT_VERSION, "saliency_plot_poster_combined.svg"), 
       combined_saliency_plot, width = 10, height = 5, bg = "white", device = "pdf")
`geom_smooth()` using formula = 'y ~ x'
`geom_smooth()` using formula = 'y ~ x'

Saliency lmers

The singular effect for the baseline window here throws me off. When I add in a random effect for the target image the effect disappears?

## Baseline window looking
looking_data_summarized <- looking_data_summarized |>
  mutate(age_in_months = SubjectInfo.testAge / 30)

baseline_looking_image <- lmer(scale(mean_target_looking_baseline_window) ~ scale(MeanSaliencyDiff) 
                    + (1 | SubjectInfo.subjID)
                    + (1 | Trials.targetImage)
                    + (1 | Trials.ordinal)
                    + (1 | Trials.imagePair), 
                    data = looking_data_summarized)
boundary (singular) fit: see help('isSingular')
baseline_looking_imagepair <- lmer(scale(mean_target_looking_baseline_window) ~ scale(MeanSaliencyDiff) 
                    + (1 | SubjectInfo.subjID)
                    + (1 | Trials.imagePair), 
                    data = looking_data_summarized)
boundary (singular) fit: see help('isSingular')
critical_looking_image <- lmer(scale(mean_target_looking_critical_window) ~ scale(MeanSaliencyDiff)
                    + (1 | SubjectInfo.subjID)
                     + (1 | Trials.imagePair)
                    + (1 | Trials.targetImage), 
                    data = looking_data_summarized)

baseline_corrected_looking_image <- lmer(scale(corrected_target_looking) ~ scale(MeanSaliencyDiff)
                    + (1 | SubjectInfo.subjID)
                     + (1 | Trials.imagePair)
                    + (1 | Trials.targetImage), 
                    data = looking_data_summarized)

baseline_covariate_looking_image <- lmer(scale(mean_target_looking_critical_window) ~ scale(MeanSaliencyDiff)
                    + scale(mean_target_looking_baseline_window)
                    + (1 |  SubjectInfo.subjID) 
                    + (1 | Trials.imagePair)
                    + (1|Trials.targetImage), 
                    data = looking_data_summarized)


summary(baseline_looking_image)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: 
scale(mean_target_looking_baseline_window) ~ scale(MeanSaliencyDiff) +  
    (1 | SubjectInfo.subjID) + (1 | Trials.targetImage) + (1 |  
    Trials.ordinal) + (1 | Trials.imagePair)
   Data: looking_data_summarized

REML criterion at convergence: 6998.2

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-1.97410 -0.71028 -0.01657  0.75334  1.91832 

Random effects:
 Groups             Name        Variance Std.Dev.
 SubjectInfo.subjID (Intercept) 0.000000 0.00000 
 Trials.ordinal     (Intercept) 0.008331 0.09127 
 Trials.targetImage (Intercept) 0.027617 0.16618 
 Trials.imagePair   (Intercept) 0.002946 0.05428 
 Residual                       0.965507 0.98260 
Number of obs: 2476, groups:  
SubjectInfo.subjID, 91; Trials.ordinal, 52; Trials.targetImage, 24; Trials.imagePair, 16

Fixed effects:
                        Estimate Std. Error       df t value Pr(>|t|)
(Intercept)              0.02535    0.04488 14.00802   0.565    0.581
scale(MeanSaliencyDiff)  0.03824    0.03363 33.08656   1.137    0.264

Correlation of Fixed Effects:
            (Intr)
scl(MnSlnD) 0.026 
optimizer (nloptwrap) convergence code: 0 (OK)
boundary (singular) fit: see help('isSingular')
summary(baseline_looking_imagepair)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: 
scale(mean_target_looking_baseline_window) ~ scale(MeanSaliencyDiff) +  
    (1 | SubjectInfo.subjID) + (1 | Trials.imagePair)
   Data: looking_data_summarized

REML criterion at convergence: 7030.1

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-1.73380 -0.74024 -0.02707  0.75524  1.72027 

Random effects:
 Groups             Name        Variance Std.Dev.
 SubjectInfo.subjID (Intercept) 0.0000   0.0000  
 Trials.imagePair   (Intercept) 0.0000   0.0000  
 Residual                       0.9974   0.9987  
Number of obs: 2476, groups:  SubjectInfo.subjID, 91; Trials.imagePair, 16

Fixed effects:
                         Estimate Std. Error        df t value Pr(>|t|)   
(Intercept)             1.638e-15  2.007e-02 2.474e+03   0.000  1.00000   
scale(MeanSaliencyDiff) 5.492e-02  2.007e-02 2.474e+03   2.736  0.00626 **
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr)
scl(MnSlnD) 0.000 
optimizer (nloptwrap) convergence code: 0 (OK)
boundary (singular) fit: see help('isSingular')
summary(critical_looking_image)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: 
scale(mean_target_looking_critical_window) ~ scale(MeanSaliencyDiff) +  
    (1 | SubjectInfo.subjID) + (1 | Trials.imagePair) + (1 |  
    Trials.targetImage)
   Data: looking_data_summarized

REML criterion at convergence: 6903.9

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.32798 -0.72305  0.08992  0.77506  2.29301 

Random effects:
 Groups             Name        Variance Std.Dev.
 SubjectInfo.subjID (Intercept) 0.036293 0.19051 
 Trials.targetImage (Intercept) 0.057970 0.24077 
 Trials.imagePair   (Intercept) 0.008672 0.09313 
 Residual                       0.904240 0.95092 
Number of obs: 2476, groups:  
SubjectInfo.subjID, 91; Trials.targetImage, 24; Trials.imagePair, 16

Fixed effects:
                        Estimate Std. Error       df t value Pr(>|t|)
(Intercept)              0.01168    0.06139 25.56388   0.190    0.851
scale(MeanSaliencyDiff)  0.04125    0.04123 42.12734   1.001    0.323

Correlation of Fixed Effects:
            (Intr)
scl(MnSlnD) 0.027 
summary(baseline_corrected_looking_image)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: scale(corrected_target_looking) ~ scale(MeanSaliencyDiff) + (1 |  
    SubjectInfo.subjID) + (1 | Trials.imagePair) + (1 | Trials.targetImage)
   Data: looking_data_summarized

REML criterion at convergence: 7006

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.86800 -0.61847 -0.02582  0.66003  2.81873 

Random effects:
 Groups             Name        Variance Std.Dev.
 SubjectInfo.subjID (Intercept) 0.020513 0.14322 
 Trials.targetImage (Intercept) 0.016016 0.12655 
 Trials.imagePair   (Intercept) 0.004581 0.06768 
 Residual                       0.961104 0.98036 
Number of obs: 2476, groups:  
SubjectInfo.subjID, 91; Trials.targetImage, 24; Trials.imagePair, 16

Fixed effects:
                         Estimate Std. Error        df t value Pr(>|t|)
(Intercept)             -0.009580   0.040032 24.752795  -0.239    0.813
scale(MeanSaliencyDiff)  0.005423   0.029624 33.456665   0.183    0.856

Correlation of Fixed Effects:
            (Intr)
scl(MnSlnD) 0.022 
summary(baseline_covariate_looking_image)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: 
scale(mean_target_looking_critical_window) ~ scale(MeanSaliencyDiff) +  
    scale(mean_target_looking_baseline_window) + (1 | SubjectInfo.subjID) +  
    (1 | Trials.imagePair) + (1 | Trials.targetImage)
   Data: looking_data_summarized

REML criterion at convergence: 6696.6

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.76241 -0.71706  0.06881  0.74548  2.39607 

Random effects:
 Groups             Name        Variance Std.Dev.
 SubjectInfo.subjID (Intercept) 0.03541  0.18817 
 Trials.targetImage (Intercept) 0.04193  0.20476 
 Trials.imagePair   (Intercept) 0.00619  0.07868 
 Residual                       0.83069  0.91142 
Number of obs: 2476, groups:  
SubjectInfo.subjID, 91; Trials.targetImage, 24; Trials.imagePair, 16

Fixed effects:
                                            Estimate Std. Error        df
(Intercept)                                3.574e-03  5.385e-02 2.773e+01
scale(MeanSaliencyDiff)                    3.430e-02  3.673e-02 4.549e+01
scale(mean_target_looking_baseline_window) 2.804e-01  1.876e-02 2.436e+03
                                           t value Pr(>|t|)    
(Intercept)                                  0.066    0.948    
scale(MeanSaliencyDiff)                      0.934    0.355    
scale(mean_target_looking_baseline_window)  14.944   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) s(MSD)
scl(MnSlnD)  0.027       
scl(mn____) -0.009 -0.016

Do we see an interaction with window type? More of an effect of saliency in baseline vs critical?

trials_window_type_separated <- looking_data_summarized |>
  pivot_longer(cols=c(mean_target_looking_critical_window, mean_target_looking_baseline_window), names_to="window_type", values_to="target_looking") |>
  mutate(window_type = str_replace(window_type, "mean_target_looking_", "")) |>
  mutate(trial_window_c = case_when(
    window_type=="critical_window" ~ 0.5,
    window_type=="baseline_window" ~ -0.5))

window_type_looking <- lmer(scale(target_looking) ~ trial_window_c*scale(MeanSaliencyDiff) + 
                    + scale(AoA_Est_target)
                    + (1 | SubjectInfo.subjID) 
                    + (1 | Trials.ordinal)
                    + (1 | Trials.targetImage), 
                    data = trials_window_type_separated)

summary(window_type_looking)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: scale(target_looking) ~ trial_window_c * scale(MeanSaliencyDiff) +  
    +scale(AoA_Est_target) + (1 | SubjectInfo.subjID) + (1 |  
    Trials.ordinal) + (1 | Trials.targetImage)
   Data: trials_window_type_separated

REML criterion at convergence: 13873.6

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.20398 -0.72783  0.03463  0.76740  2.06713 

Random effects:
 Groups             Name        Variance Std.Dev.
 SubjectInfo.subjID (Intercept) 0.012554 0.11205 
 Trials.ordinal     (Intercept) 0.007444 0.08628 
 Trials.targetImage (Intercept) 0.026501 0.16279 
 Residual                       0.937535 0.96826 
Number of obs: 4952, groups:  
SubjectInfo.subjID, 91; Trials.ordinal, 52; Trials.targetImage, 24

Fixed effects:
                                         Estimate Std. Error         df t value
(Intercept)                             1.968e-02  4.097e-02  3.094e+01   0.480
trial_window_c                          1.763e-01  2.752e-02  4.793e+03   6.407
scale(MeanSaliencyDiff)                 2.773e-02  2.671e-02  9.004e+01   1.038
scale(AoA_Est_target)                  -1.040e-01  3.474e-02  2.163e+01  -2.993
trial_window_c:scale(MeanSaliencyDiff)  6.623e-03  2.752e-02  4.793e+03   0.241
                                       Pr(>|t|)    
(Intercept)                             0.63433    
trial_window_c                         1.63e-10 ***
scale(MeanSaliencyDiff)                 0.30186    
scale(AoA_Est_target)                   0.00678 ** 
trial_window_c:scale(MeanSaliencyDiff)  0.80984    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) trl_w_ s(MSD) s(AA_E
tril_wndw_c 0.000                      
scl(MnSlnD) 0.026  0.000               
scl(AA_Es_) 0.014  0.000  0.033        
trl__:(MSD) 0.000  0.000  0.000  0.000 

Checking the difference between baseline window looking and baseline-corrected looking even though that is a little strange

trials_window_type_separated_baselinecorrected <- looking_data_summarized |>
  mutate(corrected_target_looking = scale(corrected_target_looking),
         mean_target_looking_baseline_window = scale(mean_target_looking_baseline_window)) |>
  pivot_longer(cols=c(corrected_target_looking, mean_target_looking_baseline_window), names_to="window_type", values_to="target_looking") |>
  mutate(window_type = str_replace(window_type, "mean_target_looking_", "")) |>
  mutate(trial_window_c = case_when(
    window_type=="corrected_target_looking" ~ 0.5,
    window_type=="baseline_window" ~ -0.5))

window_type_looking_baselinecorrected <- lmer(scale(target_looking) ~ trial_window_c*scale(MeanSaliencyDiff) + 
                    + (1 | SubjectInfo.subjID)
                    + (1 | Trials.targetImage), 
                    data = trials_window_type_separated_baselinecorrected)
boundary (singular) fit: see help('isSingular')
summary(window_type_looking_baselinecorrected)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: scale(target_looking) ~ trial_window_c * scale(MeanSaliencyDiff) +  
    +(1 | SubjectInfo.subjID) + (1 | Trials.targetImage)
   Data: trials_window_type_separated_baselinecorrected

REML criterion at convergence: 14048.7

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.83311 -0.68017 -0.03613  0.71390  2.70289 

Random effects:
 Groups             Name        Variance  Std.Dev. 
 SubjectInfo.subjID (Intercept) 4.746e-16 2.178e-08
 Trials.targetImage (Intercept) 9.233e-03 9.609e-02
 Residual                       9.904e-01 9.952e-01
Number of obs: 4952, groups:  SubjectInfo.subjID, 91; Trials.targetImage, 24

Fixed effects:
                                         Estimate Std. Error         df t value
(Intercept)                             7.939e-03  2.453e-02  2.235e+01   0.324
trial_window_c                         -2.035e-15  2.828e-02  4.926e+03   0.000
scale(MeanSaliencyDiff)                 2.427e-02  2.145e-02  4.961e+01   1.132
trial_window_c:scale(MeanSaliencyDiff) -4.930e-02  2.829e-02  4.926e+03  -1.743
                                       Pr(>|t|)  
(Intercept)                              0.7492  
trial_window_c                           1.0000  
scale(MeanSaliencyDiff)                  0.2632  
trial_window_c:scale(MeanSaliencyDiff)   0.0814 .
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) trl_w_ s(MSD)
tril_wndw_c 0.000               
scl(MnSlnD) 0.027  0.000        
trl__:(MSD) 0.000  0.000  0.000 
optimizer (nloptwrap) convergence code: 0 (OK)
boundary (singular) fit: see help('isSingular')

Collapsing across trial types for a single image pair

looking_data_summarized_image_pair <- looking_data_summarized %>%
  mutate(across(
    c(mean_target_looking_baseline_window, MeanSaliencyDiff),
    ~ ifelse(grepl("distractor", Trials.trialID), -.x, .x)
  ))
baseline_looking_collapsed <- lmer(scale(mean_target_looking_baseline_window) ~ scale(MeanSaliencyDiff)
                    + (1 | SubjectInfo.subjID)
                    + (1 | Trials.imagePair), 
                    data = looking_data_summarized_image_pair)
boundary (singular) fit: see help('isSingular')
summary(baseline_looking_collapsed)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: 
scale(mean_target_looking_baseline_window) ~ scale(MeanSaliencyDiff) +  
    (1 | SubjectInfo.subjID) + (1 | Trials.imagePair)
   Data: looking_data_summarized_image_pair

REML criterion at convergence: 7033.6

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-1.74206 -0.84535  0.03319  0.83807  1.80623 

Random effects:
 Groups             Name        Variance  Std.Dev. 
 SubjectInfo.subjID (Intercept) 2.831e-17 5.321e-09
 Trials.imagePair   (Intercept) 9.848e-04 3.138e-02
 Residual                       9.980e-01 9.990e-01
Number of obs: 2476, groups:  SubjectInfo.subjID, 91; Trials.imagePair, 16

Fixed effects:
                         Estimate Std. Error        df t value Pr(>|t|)  
(Intercept)             1.465e-04  2.156e-02 1.406e+01   0.007   0.9947  
scale(MeanSaliencyDiff) 3.893e-02  2.156e-02 1.404e+01   1.805   0.0925 .
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr)
scl(MnSlnD) 0.000 
optimizer (nloptwrap) convergence code: 0 (OK)
boundary (singular) fit: see help('isSingular')

checking variance across predictors

looking_data_summarized %>%
  group_by(Trials.targetImage) %>%
  summarize(var_saliency = var(MeanSaliencyDiff),
            var_similarity = var(image_similarity))
# A tibble: 24 × 3
   Trials.targetImage var_saliency var_similarity
   <chr>                     <dbl>          <dbl>
 1 acorn                   0.00567        0.00539
 2 bulldozer               0.00338        0.00843
 3 butter                  0              0      
 4 cheese                  0.00935        0.0387 
 5 coconut                 0              0      
 6 cow                     0              0      
 7 eagle                   0              0      
 8 frog                    0              0      
 9 glasses                 0              0      
10 goat                    0              0      
# ℹ 14 more rows

what about across excluded participants as well: this is a little sketchy.

baseline_looking_image_full_sample <- lmer(scale(mean_target_looking_baseline_window) ~ scale(MeanSaliencyDiff)
                    + (1 | SubjectInfo.subjID)
                    + (1 | Trials.targetImage)
                    + (1 | Trials.imagePair), 
                    data = trial_summary_data |> left_join(trial_metadata))
Joining with `by = join_by(Trials.trialID, Trials.targetImage,
Trials.distractorImage, Trials.imagePair)`
Joining with `by = join_by(Trials.trialID, Trials.targetImage,
Trials.distractorImage, Trials.imagePair)`
Joining with `by = join_by(Trials.trialID, Trials.targetImage,
Trials.distractorImage, Trials.imagePair)`
Joining with `by = join_by(Trials.trialID, Trials.targetImage,
Trials.distractorImage, Trials.imagePair)`
boundary (singular) fit: see help('isSingular')
Joining with `by = join_by(Trials.trialID, Trials.targetImage,
Trials.distractorImage, Trials.imagePair)`
Joining with `by = join_by(Trials.trialID, Trials.targetImage,
Trials.distractorImage, Trials.imagePair)`
Joining with `by = join_by(Trials.trialID, Trials.targetImage,
Trials.distractorImage, Trials.imagePair)`
Joining with `by = join_by(Trials.trialID, Trials.targetImage,
Trials.distractorImage, Trials.imagePair)`
summary(baseline_looking_image_full_sample)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: 
scale(mean_target_looking_baseline_window) ~ scale(MeanSaliencyDiff) +  
    (1 | SubjectInfo.subjID) + (1 | Trials.targetImage) + (1 |  
    Trials.imagePair)
   Data: left_join(trial_summary_data, trial_metadata)

REML criterion at convergence: 10049.6

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-1.66102 -0.80979 -0.02603  0.81625  1.75542 

Random effects:
 Groups             Name        Variance Std.Dev.
 SubjectInfo.subjID (Intercept) 0.00000  0.0000  
 Trials.targetImage (Intercept) 0.01118  0.1057  
 Trials.imagePair   (Intercept) 0.00000  0.0000  
 Residual                       0.98569  0.9928  
Number of obs: 3548, groups:  
SubjectInfo.subjID, 122; Trials.targetImage, 24; Trials.imagePair, 16

Fixed effects:
                        Estimate Std. Error       df t value Pr(>|t|)  
(Intercept)              0.01129    0.02767 19.01107   0.408   0.6877  
scale(MeanSaliencyDiff)  0.05002    0.02450 40.13875   2.042   0.0478 *
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr)
scl(MnSlnD) 0.025 
optimizer (nloptwrap) convergence code: 0 (OK)
boundary (singular) fit: see help('isSingular')

saliency order effects - planning to use these!

this is interesting! order effects showing up in the baseline window

order_ranked_trials <- looking_data_summarized |>
  group_by(SubjectInfo.subjID, Trials.targetImage) |>
  arrange(Trials.ordinal, .by_group = TRUE) |>
  mutate(
    slice_num = row_number(),
    order = case_when(
      slice_num == 1 ~ -0.5,
      slice_num == 2 ~ 0.5,
      TRUE ~ NA
    )
  )
main_image_effect_ranked <- lmer(scale(mean_target_looking_baseline_window) ~ (scale(order))
                    + scale(age_in_months)
                    + scale(MeanSaliencyDiff)                                     
                    + (1 | SubjectInfo.subjID) 
                    + (1|Trials.targetImage), 
                    data = order_ranked_trials |> mutate(age_in_months = SubjectInfo.testAge / 30))
boundary (singular) fit: see help('isSingular')
# |> filter(Trials.trialType %in% c("easy", "hard")
summary(main_image_effect_ranked)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: scale(mean_target_looking_baseline_window) ~ (scale(order)) +  
    scale(age_in_months) + scale(MeanSaliencyDiff) + (1 | SubjectInfo.subjID) +  
    (1 | Trials.targetImage)
   Data: mutate(order_ranked_trials, age_in_months = SubjectInfo.testAge/30)

REML criterion at convergence: 7007.2

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-1.89727 -0.72117 -0.02422  0.76639  1.95451 

Random effects:
 Groups             Name        Variance Std.Dev.
 SubjectInfo.subjID (Intercept) 0.00000  0.0000  
 Trials.targetImage (Intercept) 0.01816  0.1348  
 Residual                       0.97515  0.9875  
Number of obs: 2476, groups:  SubjectInfo.subjID, 91; Trials.targetImage, 24

Fixed effects:
                          Estimate Std. Error         df t value Pr(>|t|)   
(Intercept)              1.512e-02  3.451e-02  1.989e+01   0.438  0.66595   
scale(order)            -6.128e-02  2.206e-02  1.080e+03  -2.778  0.00557 **
scale(age_in_months)    -8.574e-03  1.986e-02  2.451e+03  -0.432  0.66597   
scale(MeanSaliencyDiff)  4.703e-02  3.016e-02  4.441e+01   1.559  0.12603   
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) scl(r) sc(__)
scale(ordr)  0.077              
scl(g_n_mn) -0.002 -0.002       
scl(MnSlnD)  0.022 -0.064 -0.011
optimizer (nloptwrap) convergence code: 0 (OK)
boundary (singular) fit: see help('isSingular')
looking_data_first_appearance <- looking_data_summarized |>
  group_by(SubjectInfo.subjID, Trials.targetImage) |>
  arrange(Trials.ordinal, .by_group = TRUE) |>
  slice(1) |>
  ungroup()

baseline_looking_first_appearance_model <- lmer(scale(mean_target_looking_baseline_window) ~ scale(MeanSaliencyDiff) 
                    + (1 | SubjectInfo.subjID)
                    + (1 | Trials.targetImage)
                    + (1 | Trials.imagePair), 
                    data = looking_data_first_appearance)
boundary (singular) fit: see help('isSingular')
# |> filter(Trials.trialType %in% c("easy", "hard")
summary(baseline_looking_first_appearance_model)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: 
scale(mean_target_looking_baseline_window) ~ scale(MeanSaliencyDiff) +  
    (1 | SubjectInfo.subjID) + (1 | Trials.targetImage) + (1 |  
    Trials.imagePair)
   Data: looking_data_first_appearance

REML criterion at convergence: 5484.5

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-1.8970 -0.7264 -0.0091  0.7935  1.8791 

Random effects:
 Groups             Name        Variance Std.Dev.
 SubjectInfo.subjID (Intercept) 0.003995 0.06321 
 Trials.targetImage (Intercept) 0.015223 0.12338 
 Trials.imagePair   (Intercept) 0.000000 0.00000 
 Residual                       0.977207 0.98854 
Number of obs: 1936, groups:  
SubjectInfo.subjID, 91; Trials.targetImage, 24; Trials.imagePair, 16

Fixed effects:
                         Estimate Std. Error        df t value Pr(>|t|)  
(Intercept)              0.001781   0.034434 22.555797   0.052   0.9592  
scale(MeanSaliencyDiff)  0.070894   0.031748 34.942005   2.233   0.0321 *
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr)
scl(MnSlnD) 0.004 
optimizer (nloptwrap) convergence code: 0 (OK)
boundary (singular) fit: see help('isSingular')

OpenCLIP training

openclip_similarities_combined <- openclip_similarities |>
  rename(word_a = word1, word_b = word2) |>
  bind_rows(
    openclip_similarities |>
      rename(word_a = word2, word_b = word1)
  )
looking_data_w_openclip <- looking_data_summarized |>
  select(-text_similarity, -multimodal_similarity, -image_similarity) |>
  left_join(openclip_similarities_combined, by = c("Trials.distractorImage"="word_a", "Trials.targetImage"="word_b"))
Warning in left_join(select(looking_data_summarized, -text_similarity, -multimodal_similarity, : Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 1 of `x` matches multiple rows in `y`.
ℹ Row 6 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship =
  "many-to-many"` to silence this warning.
common_cols <- intersect(colnames(looking_data_w_openclip), colnames(looking_data_summarized))

looking_data_w_openclip_clip <- looking_data_w_openclip |> bind_rows(
    looking_data_summarized %>%
      mutate(epoch = 33) %>%
      select(all_of(common_cols), epoch)
  )


openclip_data_summarized <- summarize_similarity_data(looking_data_w_openclip, extra_fields=c("epoch"))
openclip_clip_data_summarized <- summarize_similarity_data(looking_data_w_openclip_clip, extra_fields=c("epoch"))
openclip_age_half_summarized <- looking_data_w_openclip |>
  add_age_split() |>
  summarize_similarity_data(extra_fields = c("age_half", "epoch"))

# Function to calculate Pearson's correlation per epoch
calculate_correlations <- function(data, x_var, y_var, group_var = c("epoch"), conf_level = 0.95) {
  data |>
    group_by(across(all_of(group_var))) |>
    summarize(
      {
        cor_test <- cor.test(.data[[x_var]], .data[[y_var]], method = "pearson", conf.level = conf_level)
        tibble(
          pearson_cor = cor_test$estimate,
          p_value = cor_test$p.value,
          ci_lower = cor_test$conf.int[1],
          ci_upper = cor_test$conf.int[2]
        )
      },
      .groups = "drop"
    )
}

image_correlation_results_age_split <- calculate_correlations(openclip_age_half_summarized, "image_similarity", "mean_value", group_var=c("epoch", "age_half"))

text_correlation_results <- calculate_correlations(openclip_data_summarized, "text_similarity", "mean_value", group_var=c("epoch")) %>%
  mutate(similarity_type = "Text similarity")
image_correlation_results <- calculate_correlations(openclip_data_summarized, "image_similarity", "mean_value", group_var=c("epoch")) %>%
  mutate(similarity_type = "Image similarity")

combined_correlation_results <- bind_rows(text_correlation_results, image_correlation_results)

CCN plot

oc_plot <- ggplot(combined_correlation_results |> 
         rowwise() |> 
         mutate(epoch = unique_checkpoints[[epoch]]), 
       aes(x = log(epoch), y = pearson_cor, color = similarity_type)) +
  #geom_errorbar(aes(ymin = ci_lower, ymax = ci_upper), width = 0.1) +
  #geom_hline(yintercept = 0, linetype = "dashed", color = "gray40") +
  geom_point(size = 6, alpha = 0.8) +
  geom_smooth(alpha = 0.3, size = 0, span = 2, show.legend = FALSE) +  
  stat_smooth(geom = "line", alpha = 0.8, size = 1.5, span = 2, show.legend = FALSE) + 
  labs(x = "log (OpenCLIP epoch)",
       y = "Pearson's\ncorrelation (r)",
       color = "Similarity Type") +
  scale_color_manual(values = c(
    "Text similarity" = "#B8481C",           
    "Image similarity" = "#215D89"
  )) +  
  scale_shape_manual(values = c(`TRUE` = 16, `FALSE` = 1)) + 
  theme(
    text = element_text(size=26, face="bold"),
    legend.position = c(0.25, 0.8),  # Adjust x/y values as needed
    legend.background = element_rect(fill = "white", color = "gray80"),
    legend.key = element_blank(),
    legend.title = element_blank(),
    legend.text = element_text(size = 26, face = "bold"),
    axis.title = element_text(size = 28),
    axis.title.x = element_text(
      face = "bold", 
      size = 32,
      margin = margin(t = 15, r = 0, b = 0, l = 0)
    ),
    axis.title.y = element_text(
      face = "bold", 
      size = 32,
      margin = margin(t = 0, r = 10, b = 0, l = 0)
    ),
    axis.text = element_text(size = 30, face = "bold")
  )
Warning: A numeric `legend.position` argument in `theme()` was deprecated in ggplot2
3.5.0.
ℹ Please use the `legend.position.inside` argument of `theme()` instead.
oc_plot
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Warning: No shared levels found between `names(values)` of the manual scale and the
data's shape values.

ggsave(here("figures",PROJECT_VERSION,"oc_plot_poster.svg"),oc_plot,width=10, height=5,bg = "white",device="pdf")
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Warning: No shared levels found between `names(values)` of the manual scale and the
data's shape values.

Comparing to random values

library(dplyr)
library(purrr)

get_randomized_correlation <- function(data, sim_type, group_var = c("epoch")) {
  randomized_values <- data %>%
    distinct(Trials.trialID, .keep_all = TRUE) %>%
    mutate(shuffled_value = sample(mean_value)) %>%
    select(Trials.trialID, shuffled_value)
  randomized_data <- data %>%
    left_join(randomized_values, by = "Trials.trialID") %>%
    mutate(mean_value = shuffled_value) %>%
    select(-shuffled_value)
  
  calculate_correlations(randomized_data, sim_type, "mean_value", group_var = group_var)
}

image_correlations_randomized <- get_randomized_correlation(openclip_data_summarized, "image_similarity") |>
  mutate(similarity_type = "Image randomized")

text_correlations_randomized <- get_randomized_correlation(openclip_data_summarized, "text_similarity") |>
  mutate(similarity_type = "Text randomized")

n_boot <- 1000
boot_results <- map_dfr(1:n_boot, function(i) {
  image_corr <- get_randomized_correlation(openclip_data_summarized, "image_similarity") %>%
    mutate(similarity_type = "Image randomized bootstrapped", bootstrap = i)
  
  text_corr <- get_randomized_correlation(openclip_data_summarized, "text_similarity") %>%
    mutate(similarity_type = "Text randomized bootstrapped", bootstrap = i)
  
  bind_rows(image_corr, text_corr)
})
boot_summary <- boot_results %>%
  group_by(epoch, similarity_type) %>%
  summarize(
    pearson_cor = mean(pearson_cor, na.rm = TRUE),
    ci_lower = quantile(pearson_cor, 0.025, na.rm = TRUE),
    ci_upper = quantile(pearson_cor, 0.975, na.rm = TRUE),
    .groups = "drop"
  )

combined_correlation_results <- bind_rows(combined_correlation_results, image_correlations_randomized, text_correlations_randomized, boot_summary) %>%
  mutate(is_randomized = grepl("randomized", similarity_type) & !grepl("bootstrap", similarity_type))

ggplot(combined_correlation_results |> rowwise() |> mutate(epoch = unique_checkpoints[[epoch]]), aes(x = log(epoch), y = pearson_cor, color = similarity_type, alpha = is_randomized)) +
  geom_point(size = 3, aes(shape = !is.na(p_value) & p_value < 0.05)) +
  geom_smooth(span = 2, se = FALSE) +
  #geom_errorbar(aes(ymin = ci_lower, ymax = ci_upper), width = 0.1) +
   geom_hline(yintercept = 0, linetype = "dashed", color = "gray40") +
  labs(title = "Correlation Across Open-CLIP Training",
       x = "log (Epoch)",
       y = "Pearson correlation between\nbaseline-corrected prop. target looking\nand embedding similarity",
       color = "Similarity Type",
       shape = "p < 0.05") +
   scale_color_manual(values = c(
    "Text" = "#1f77b4",           # Default blue
    "Image" = "#215D89",          # Default orange
    "Image randomized bootstrapped" = "#ffbb78",           # Lighter orange
    "Text randomized bootstrapped" = "#1fa2b0",
    "Image randomized" = "#ffbb78",  
    "Text randomized" = "#1fa2b0"
  )) +
  scale_alpha_manual(values = c(`TRUE` = 0.3, `FALSE` = 1)) +
  guides(alpha = "none") +
  scale_shape_manual(values = c(`TRUE` = 16, `FALSE` = 1)) +  # Filled vs hollow dots for significance
  theme_minimal()
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'

It is unadvised to do model comparisons with such few items! However, interesting future direction.

checking out epoch 3 which had the highest correlation

still had to remove the random slope for image_similarity because of a singular fit but we definitely see a stronger effect

stopifnot(nrow(looking_data_w_openclip |> filter(epoch == 3)) == nrow(looking_data_summarized))
epoch3_data_summarized <- summarize_similarity_data(looking_data_w_openclip |> filter(epoch == 3))
openclip_data_long <-  epoch3_data_summarized |>  
  pivot_longer(cols = c("text_similarity", "image_similarity"), names_to = "sim_type", values_to = "similarity")
all_clip_plot <- multiple_similarity_effects_plot(openclip_data_long, "similarity", group_var="sim_type", input_title="Looking time and Open CLIP epoch 3 embedding correlations")
all_clip_plot
`geom_smooth()` using formula = 'y ~ x'
`geom_smooth()` using formula = 'y ~ x'

Comparing models from different epochs

library(MuMIn)
epoch3_image_effect <- fit_image_model(looking_data_w_openclip |> filter(epoch == 3))
summary(epoch3_image_effect)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: 
scale(corrected_target_looking) ~ scale(image_similarity) * scale(age_in_months) +  
    scale(AoA_Est_target) + scale(MeanSaliencyDiff) + (1 | SubjectInfo.subjID) +  
    (1 | Trials.targetImage)
   Data: mutate(summary_data, age_in_months = SubjectInfo.testAge/30)

REML criterion at convergence: 7005.6

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.81337 -0.62233 -0.01623  0.66566  2.78508 

Random effects:
 Groups             Name        Variance Std.Dev.
 SubjectInfo.subjID (Intercept) 0.017035 0.13052 
 Trials.targetImage (Intercept) 0.007234 0.08506 
 Residual                       0.961386 0.98050 
Number of obs: 2476, groups:  SubjectInfo.subjID, 91; Trials.targetImage, 24

Fixed effects:
                                               Estimate Std. Error         df
(Intercept)                                  -7.377e-03  3.003e-02  2.768e+01
scale(image_similarity)                      -5.637e-02  2.317e-02  1.240e+02
scale(age_in_months)                          5.790e-02  2.402e-02  9.101e+01
scale(AoA_Est_target)                        -7.868e-02  2.680e-02  2.326e+01
scale(MeanSaliencyDiff)                       2.785e-03  2.507e-02  3.511e+01
scale(image_similarity):scale(age_in_months) -3.601e-02  1.977e-02  2.384e+03
                                             t value Pr(>|t|)   
(Intercept)                                   -0.246  0.80779   
scale(image_similarity)                       -2.433  0.01641 * 
scale(age_in_months)                           2.411  0.01792 * 
scale(AoA_Est_target)                         -2.936  0.00737 **
scale(MeanSaliencyDiff)                        0.111  0.91216   
scale(image_similarity):scale(age_in_months)  -1.822  0.06864 . 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) scl(_) sc(__) s(AA_E s(MSD)
scl(mg_sml) -0.004                            
scl(g_n_mn)  0.003 -0.009                     
scl(AA_Es_)  0.009 -0.232  0.011              
scl(MnSlnD)  0.018 -0.015 -0.009  0.027       
scl(_):(__) -0.004 -0.010  0.009  0.010 -0.007
epoch32_image_effect <- fit_image_model(looking_data_w_openclip |> filter(epoch == 28))
summary(epoch32_image_effect)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: 
scale(corrected_target_looking) ~ scale(image_similarity) * scale(age_in_months) +  
    scale(AoA_Est_target) + scale(MeanSaliencyDiff) + (1 | SubjectInfo.subjID) +  
    (1 | Trials.targetImage)
   Data: mutate(summary_data, age_in_months = SubjectInfo.testAge/30)

REML criterion at convergence: 7010.1

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.82004 -0.62033 -0.01545  0.67083  2.79780 

Random effects:
 Groups             Name        Variance Std.Dev.
 SubjectInfo.subjID (Intercept) 0.01717  0.1310  
 Trials.targetImage (Intercept) 0.01005  0.1002  
 Residual                       0.96185  0.9807  
Number of obs: 2476, groups:  SubjectInfo.subjID, 91; Trials.targetImage, 24

Fixed effects:
                                               Estimate Std. Error         df
(Intercept)                                  -8.310e-03  3.202e-02  2.745e+01
scale(image_similarity)                      -4.673e-02  2.525e-02  7.178e+01
scale(age_in_months)                          5.803e-02  2.405e-02  9.100e+01
scale(AoA_Est_target)                        -8.175e-02  2.874e-02  2.256e+01
scale(MeanSaliencyDiff)                       1.432e-03  2.658e-02  3.786e+01
scale(image_similarity):scale(age_in_months) -2.031e-02  1.967e-02  2.380e+03
                                             t value Pr(>|t|)   
(Intercept)                                   -0.260  0.79714   
scale(image_similarity)                       -1.851  0.06835 . 
scale(age_in_months)                           2.413  0.01783 * 
scale(AoA_Est_target)                         -2.845  0.00928 **
scale(MeanSaliencyDiff)                        0.054  0.95732   
scale(image_similarity):scale(age_in_months)  -1.032  0.30206   
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) scl(_) sc(__) s(AA_E s(MSD)
scl(mg_sml) -0.008                            
scl(g_n_mn)  0.003 -0.007                     
scl(AA_Es_)  0.012 -0.212  0.010              
scl(MnSlnD)  0.021  0.010 -0.009  0.025       
scl(_):(__) -0.003 -0.002  0.004  0.003 -0.006
fully_trained_clip_effect <- fit_image_model(looking_data_summarized)
summary(fully_trained_clip_effect)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: 
scale(corrected_target_looking) ~ scale(image_similarity) * scale(age_in_months) +  
    scale(AoA_Est_target) + scale(MeanSaliencyDiff) + (1 | SubjectInfo.subjID) +  
    (1 | Trials.targetImage)
   Data: mutate(summary_data, age_in_months = SubjectInfo.testAge/30)

REML criterion at convergence: 7009.3

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.82154 -0.62402 -0.01811  0.67410  2.81296 

Random effects:
 Groups             Name        Variance Std.Dev.
 SubjectInfo.subjID (Intercept) 0.017207 0.13118 
 Trials.targetImage (Intercept) 0.009785 0.09892 
 Residual                       0.961620 0.98062 
Number of obs: 2476, groups:  SubjectInfo.subjID, 91; Trials.targetImage, 24

Fixed effects:
                                               Estimate Std. Error         df
(Intercept)                                  -8.565e-03  3.185e-02  2.657e+01
scale(image_similarity)                      -4.591e-02  2.429e-02  1.146e+02
scale(age_in_months)                          5.786e-02  2.406e-02  9.098e+01
scale(AoA_Est_target)                        -7.913e-02  2.886e-02  2.245e+01
scale(MeanSaliencyDiff)                       6.125e-04  2.645e-02  3.581e+01
scale(image_similarity):scale(age_in_months) -2.562e-02  1.970e-02  2.385e+03
                                             t value Pr(>|t|)  
(Intercept)                                   -0.269   0.7901  
scale(image_similarity)                       -1.890   0.0612 .
scale(age_in_months)                           2.405   0.0182 *
scale(AoA_Est_target)                         -2.742   0.0118 *
scale(MeanSaliencyDiff)                        0.023   0.9817  
scale(image_similarity):scale(age_in_months)  -1.301   0.1935  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) scl(_) sc(__) s(AA_E s(MSD)
scl(mg_sml) -0.004                            
scl(g_n_mn)  0.003 -0.005                     
scl(AA_Es_)  0.011 -0.255  0.010              
scl(MnSlnD)  0.021  0.029 -0.009  0.019       
scl(_):(__)  0.000 -0.008  0.006  0.003 -0.010

Plotting full lmer model values across epochs

library(dplyr)
library(MuMIn)
library(ggplot2)
library(patchwork)  # for combining plots

Attaching package: 'patchwork'
The following object is masked from 'package:cowplot':

    align_plots
# Define the epochs you want to analyze
epochs <- 1:33

# Initialize results storage
results <- data.frame(epoch = numeric(),
                      r2_marginal = numeric(),
                      corr_similarity_behavior = numeric())

common_cols <- intersect(colnames(looking_data_w_openclip), colnames(looking_data_summarized))

# Bind rows using only common columns, adding epoch = 33 to summarized data
looking_data_w_openclip_clip <- looking_data_w_openclip %>%
  bind_rows(
    looking_data_summarized %>%
      mutate(epoch = 33) %>%
      select(all_of(common_cols), epoch)
  )

# Loop over epochs
for (e in epochs) {
  df <- looking_data_w_openclip_clip |> filter(epoch == e)

  # Skip if not enough data
  if (nrow(df) < 10) next

  # Fit model (update to your exact model formula if needed)
  model <- fit_image_model(df)

  # Extract marginal R²
  r2 <- tryCatch(r.squaredGLMM(model)[1, "R2m"], error = function(e) NA)

  # Compute Pearson correlation between image similarity and behavior
 coefs <- summary(model)$coefficients
  p_val <- NA
  if ("scale(image_similarity)" %in% rownames(coefs)) {
    p_val <- coefs["scale(image_similarity)", "Pr(>|t|)"]
  }
  # Store results
  results <- rbind(results, data.frame(epoch = e,
                                       r2_marginal = r2,
                                       p_value = p_val))
}

# Plot R²
plot_r2 <- ggplot(results, aes(x = epoch, y = r2_marginal)) +
  geom_smooth(color = "steelblue", size = 1.2) +
  geom_point(size = 2) +
  labs(title = "Marginal R² of image similarity LMER vs. CLIP Epoch",
       x = "Epoch",
       y = "Marginal R²") +
  theme_minimal()

# Plot correlation
plot_corr <- ggplot(results, aes(x = epoch, y = p_value)) +
  geom_smooth(color = "firebrick", size = 1.2) +
  geom_point(size = 2) +
  geom_hline(yintercept = 0.05, linetype = "dotted", color = "gray40") +
  annotate("text", x = max(results$epoch), y = 0.055, 
           label = "p = 0.05", hjust = 4, vjust = 0, size = 4, color = "gray40") +
  labs(title = "p-value of image similarity in full LMER vs. CLIP epoch",
       x = "Epoch",
       y = "p-value") +
  theme_minimal()

# Combine and display
plot_r2 / plot_corr
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'

Looking time across epochs

epoch_model <- lmer(
    scale(corrected_target_looking) ~ 
      scale(image_similarity) * scale(epoch) + 
      scale(age_in_months) +
      scale(AoA_Est_target) + 
      scale(MeanSaliencyDiff) +
      (1 | SubjectInfo.subjID) +
      (1 | Trials.targetImage) +
      (1 | Trials.imagePair),
    data = looking_data_w_openclip_clip |> 
      mutate(age_in_months = SubjectInfo.testAge / 30)
  )
summary(epoch_model)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: 
scale(corrected_target_looking) ~ scale(image_similarity) * scale(epoch) +  
    scale(age_in_months) + scale(AoA_Est_target) + scale(MeanSaliencyDiff) +  
    (1 | SubjectInfo.subjID) + (1 | Trials.targetImage) + (1 |  
    Trials.imagePair)
   Data: 
mutate(looking_data_w_openclip_clip, age_in_months = SubjectInfo.testAge/30)

REML criterion at convergence: 225194.3

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-3.0236 -0.6281 -0.0126  0.6838  3.1906 

Random effects:
 Groups             Name        Variance Std.Dev.
 SubjectInfo.subjID (Intercept) 0.05218  0.2284  
 Trials.targetImage (Intercept) 0.02820  0.1679  
 Trials.imagePair   (Intercept) 0.01562  0.1250  
 Residual                       0.91538  0.9568  
Number of obs: 81708, groups:  
SubjectInfo.subjID, 91; Trials.targetImage, 24; Trials.imagePair, 16

Fixed effects:
                                       Estimate Std. Error         df t value
(Intercept)                          -1.973e-02  5.235e-02  4.227e+01  -0.377
scale(image_similarity)              -1.372e-03  6.505e-03  3.843e+04  -0.211
scale(epoch)                          2.719e-04  3.714e-03  8.051e+04   0.073
scale(age_in_months)                  5.515e-02  2.404e-02  8.906e+01   2.294
scale(AoA_Est_target)                -6.191e-02  3.391e-02  1.954e+01  -1.826
scale(MeanSaliencyDiff)               1.300e-02  2.628e-02  2.563e+01   0.494
scale(image_similarity):scale(epoch) -4.642e-04  3.581e-03  7.033e+04  -0.130
                                     Pr(>|t|)  
(Intercept)                            0.7082  
scale(image_similarity)                0.8330  
scale(epoch)                           0.9416  
scale(age_in_months)                   0.0241 *
scale(AoA_Est_target)                  0.0832 .
scale(MeanSaliencyDiff)                0.6252  
scale(image_similarity):scale(epoch)   0.8969  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) scl(_) scl(p) sc(__) s(AA_E s(MSD)
scl(mg_sml) -0.016                                   
scale(epch)  0.000 -0.326                            
scl(g_n_mn)  0.006  0.000  0.000                     
scl(AA_Es_)  0.011 -0.001  0.000  0.000              
scl(MnSlnD)  0.023  0.010 -0.003  0.000 -0.025       
scl(mg_):() -0.023  0.669 -0.007  0.000 -0.001  0.008
r.squaredGLMM(epoch_model)
             R2m       R2c
[1,] 0.007045641 0.1013007

Additional models

create_model_plots <- function(input_similarities, median_age, name="CVCL") {
  similarities_combined <- input_similarities |>
    rename(word_a = word1, word_b = word2) |>
    bind_rows(
      input_similarities |>
        rename(word_a = word2, word_b = word1)
    )
  
  looking_data_w_model <- looking_data_summarized |>
    select(-text_similarity, -multimodal_similarity, -image_similarity) |>
    left_join(similarities_combined, by = c("Trials.distractorImage"="word_a", "Trials.targetImage"="word_b"))
  
  data_summarized <- summarize_similarity_data(looking_data_w_model)
  
  age_half_summarized <- looking_data_w_model |>
    add_age_split() |>
    summarize_similarity_data(extra_fields = c("age_half"))
  
  p <- generate_multimodal_plots(data_summarized, name)
  return(list(
    plot = p,
    data = looking_data_w_model
  ))
}

CVCL

cvcl_similarities
       word1   word2 image_similarity text_similarity multimodal_similarity
1     turkey    swan       0.17444706      0.02558377            0.13672301
2     turtle   horse       0.17161047      0.12650073            0.08804899
3     cheese     mud       0.12267336      0.04901435           -0.03633275
4   squirrel   eagle       0.22197309      0.17859630            0.20261563
5     potato glasses       0.15317366      0.07150085            0.11340694
6  bulldozer  orange      -0.02197811      0.02320719           -0.03731379
7      snail     cow      -0.02817563      0.03850634           -0.04224624
8      acorn     key       0.19834010      0.13790375            0.22495553
9     turkey    goat       0.15390062      0.25035784            0.15649948
10    turtle    frog       0.30299562     -0.05592079            0.02503634
11    cheese  butter       0.35650474      0.10390703            0.08898249
12  squirrel  monkey       0.34328401      0.04259083            0.20561886
13    potato     pot       0.06314112     -0.02086087            0.06438187
14 bulldozer tractor       0.43432832      0.10392722            0.32970104
15     snail    worm       0.07686698      0.13534546            0.05373919
16     acorn coconut       0.34766978      1.00000012            0.69287187
17    turkey    swan       0.17444706      0.02558377            0.13672301
18    turtle   horse       0.17161047      0.12650073            0.08804899
19    cheese     mud       0.12267336      0.04901435           -0.03633275
20  squirrel   eagle       0.22197309      0.17859630            0.20261563
21    potato glasses       0.15317366      0.07150085            0.11340694
22 bulldozer  orange      -0.02197811      0.02320719           -0.03731379
23     snail     cow      -0.02817563      0.03850634           -0.04224624
24     acorn     key       0.19834010      0.13790375            0.22495553
25    turkey    goat       0.15390062      0.25035784            0.15649948
26    turtle    frog       0.30299562     -0.05592079            0.02503634
27    cheese  butter       0.35650474      0.10390703            0.08898249
28  squirrel  monkey       0.34328401      0.04259083            0.20561886
29    potato     pot       0.06314112     -0.02086087            0.06438187
30 bulldozer tractor       0.43432832      0.10392722            0.32970104
31     snail    worm       0.07686698      0.13534546            0.05373919
32     acorn coconut       0.34766978      1.00000012            0.69287187
cvcl_summary <- create_model_plots(cvcl_similarities, name="CVCL", median_age=input_median_age)
Warning in left_join(select(looking_data_summarized, -text_similarity, -multimodal_similarity, : Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 1 of `x` matches multiple rows in `y`.
ℹ Row 6 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship =
  "many-to-many"` to silence this warning.
Registered S3 methods overwritten by 'broom':
  method        from 
  nobs.fitdistr MuMIn
  nobs.multinom MuMIn
`geom_smooth()` using formula = 'y ~ x'
`geom_smooth()` using formula = 'y ~ x'
`geom_smooth()` using formula = 'y ~ x'
Warning: ggrepel: 60 unlabeled data points (too many overlaps). Consider
increasing max.overlaps
Warning: ggrepel: 56 unlabeled data points (too many overlaps). Consider
increasing max.overlaps
Warning: ggrepel: 60 unlabeled data points (too many overlaps). Consider
increasing max.overlaps
imagenet_vit_summary <- create_model_plots(imagenetvit_similarities, name="ImageNetVIT", median_age=input_median_age)
`geom_smooth()` using formula = 'y ~ x'
Warning: ggrepel: 4 unlabeled data points (too many overlaps). Consider
increasing max.overlaps
saycamvit_summary <- create_model_plots(saycamvit_similarities, name="SayCamVIT",median_age=input_median_age)
`geom_smooth()` using formula = 'y ~ x'
Warning: ggrepel: 7 unlabeled data points (too many overlaps). Consider
increasing max.overlaps
cvcl_model <- fit_image_model(cvcl_summary$data)
summary(cvcl_model)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: 
scale(corrected_target_looking) ~ scale(image_similarity) * scale(age_in_months) +  
    scale(AoA_Est_target) + scale(MeanSaliencyDiff) + (1 | SubjectInfo.subjID) +  
    (1 | Trials.targetImage)
   Data: mutate(summary_data, age_in_months = SubjectInfo.testAge/30)

REML criterion at convergence: 13912.5

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.92934 -0.62674 -0.02029  0.67233  2.95343 

Random effects:
 Groups             Name        Variance Std.Dev.
 SubjectInfo.subjID (Intercept) 0.03494  0.1869  
 Trials.targetImage (Intercept) 0.01582  0.1258  
 Residual                       0.94062  0.9699  
Number of obs: 4952, groups:  SubjectInfo.subjID, 91; Trials.targetImage, 24

Fixed effects:
                                               Estimate Std. Error         df
(Intercept)                                  -1.464e-02  3.544e-02  3.817e+01
scale(image_similarity)                      -9.371e-03  2.048e-02  1.640e+02
scale(age_in_months)                          5.670e-02  2.393e-02  9.002e+01
scale(AoA_Est_target)                        -8.775e-02  2.880e-02  2.194e+01
scale(MeanSaliencyDiff)                       6.229e-03  2.403e-02  5.537e+01
scale(image_similarity):scale(age_in_months) -1.591e-02  1.376e-02  4.859e+03
                                             t value Pr(>|t|)   
(Intercept)                                   -0.413  0.68175   
scale(image_similarity)                       -0.458  0.64786   
scale(age_in_months)                           2.369  0.01997 * 
scale(AoA_Est_target)                         -3.047  0.00592 **
scale(MeanSaliencyDiff)                        0.259  0.79641   
scale(image_similarity):scale(age_in_months)  -1.157  0.24752   
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) scl(_) sc(__) s(AA_E s(MSD)
scl(mg_sml)  0.002                            
scl(g_n_mn)  0.005 -0.007                     
scl(AA_Es_)  0.012 -0.182  0.006              
scl(MnSlnD)  0.025  0.115 -0.007  0.012       
scl(_):(__) -0.001  0.003  0.010  0.003 -0.004
cvcl_summary$plot
Warning: ggrepel: 60 unlabeled data points (too many overlaps). Consider
increasing max.overlaps
Warning: ggrepel: 62 unlabeled data points (too many overlaps). Consider increasing max.overlaps
ggrepel: 62 unlabeled data points (too many overlaps). Consider increasing max.overlaps

SayCAM-VIT

saycamvit_model <- fit_image_model(saycamvit_summary$data)
summary(saycamvit_model)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: 
scale(corrected_target_looking) ~ scale(image_similarity) * scale(age_in_months) +  
    scale(AoA_Est_target) + scale(MeanSaliencyDiff) + (1 | SubjectInfo.subjID) +  
    (1 | Trials.targetImage)
   Data: mutate(summary_data, age_in_months = SubjectInfo.testAge/30)

REML criterion at convergence: 7007.2

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-2.8292 -0.6194 -0.0167  0.6710  2.8328 

Random effects:
 Groups             Name        Variance Std.Dev.
 SubjectInfo.subjID (Intercept) 0.01728  0.1315  
 Trials.targetImage (Intercept) 0.01008  0.1004  
 Residual                       0.96066  0.9801  
Number of obs: 2476, groups:  SubjectInfo.subjID, 91; Trials.targetImage, 24

Fixed effects:
                                               Estimate Std. Error         df
(Intercept)                                  -9.100e-03  3.205e-02  2.673e+01
scale(image_similarity)                      -2.098e-02  2.573e-02  7.299e+01
scale(age_in_months)                          5.751e-02  2.407e-02  9.096e+01
scale(AoA_Est_target)                        -8.641e-02  2.926e-02  2.267e+01
scale(MeanSaliencyDiff)                       1.293e-03  2.659e-02  3.654e+01
scale(image_similarity):scale(age_in_months) -5.101e-02  1.976e-02  2.383e+03
                                             t value Pr(>|t|)   
(Intercept)                                   -0.284   0.7786   
scale(image_similarity)                       -0.815   0.4175   
scale(age_in_months)                           2.390   0.0189 * 
scale(AoA_Est_target)                         -2.953   0.0072 **
scale(MeanSaliencyDiff)                        0.049   0.9615   
scale(image_similarity):scale(age_in_months)  -2.582   0.0099 **
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) scl(_) sc(__) s(AA_E s(MSD)
scl(mg_sml) -0.002                            
scl(g_n_mn)  0.003 -0.004                     
scl(AA_Es_)  0.010 -0.279  0.010              
scl(MnSlnD)  0.021  0.028 -0.009  0.018       
scl(_):(__)  0.003  0.008  0.006  0.000 -0.002
saycamvit_summary$plot
Warning: ggrepel: 29 unlabeled data points (too many overlaps). Consider
increasing max.overlaps

ImageNet-VIT

imagenet_vit_model <- fit_image_model(imagenet_vit_summary$data)
summary(imagenet_vit_model)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: 
scale(corrected_target_looking) ~ scale(image_similarity) * scale(age_in_months) +  
    scale(AoA_Est_target) + scale(MeanSaliencyDiff) + (1 | SubjectInfo.subjID) +  
    (1 | Trials.targetImage)
   Data: mutate(summary_data, age_in_months = SubjectInfo.testAge/30)

REML criterion at convergence: 7010.3

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.82125 -0.62190 -0.02172  0.66775  2.83401 

Random effects:
 Groups             Name        Variance Std.Dev.
 SubjectInfo.subjID (Intercept) 0.01724  0.1313  
 Trials.targetImage (Intercept) 0.01031  0.1015  
 Residual                       0.96184  0.9807  
Number of obs: 2476, groups:  SubjectInfo.subjID, 91; Trials.targetImage, 24

Fixed effects:
                                               Estimate Std. Error         df
(Intercept)                                  -9.089e-03  3.220e-02  2.645e+01
scale(image_similarity)                      -1.220e-02  2.618e-02  4.980e+01
scale(age_in_months)                          5.747e-02  2.407e-02  9.098e+01
scale(AoA_Est_target)                        -9.012e-02  2.884e-02  2.157e+01
scale(MeanSaliencyDiff)                       1.361e-03  2.673e-02  3.600e+01
scale(image_similarity):scale(age_in_months) -3.926e-02  1.978e-02  2.384e+03
                                             t value Pr(>|t|)   
(Intercept)                                   -0.282  0.77998   
scale(image_similarity)                       -0.466  0.64320   
scale(age_in_months)                           2.388  0.01900 * 
scale(AoA_Est_target)                         -3.124  0.00501 **
scale(MeanSaliencyDiff)                        0.051  0.95968   
scale(image_similarity):scale(age_in_months)  -1.985  0.04728 * 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) scl(_) sc(__) s(AA_E s(MSD)
scl(mg_sml) -0.002                            
scl(g_n_mn)  0.003 -0.003                     
scl(AA_Es_)  0.010 -0.199  0.009              
scl(MnSlnD)  0.021  0.039 -0.009  0.019       
scl(_):(__)  0.003  0.001  0.008 -0.002 -0.001
imagenet_vit_summary$plot
Warning: ggrepel: 30 unlabeled data points (too many overlaps). Consider
increasing max.overlaps

With both SayCAM VIT and ImageNet VIT we do see this interesting interaction between age and similarity, where the influence of similarity decreases with age.

comparing similarity values across our measures

library(dplyr)
library(GGally)
Registered S3 method overwritten by 'GGally':
  method from   
  +.gg   ggplot2
library(ggplot2)

# Join all similarity datasets with trial metadata
combined_data <- cvcl_similarities %>%
  # Join with imagenetvit similarities
  left_join(imagenetvit_similarities, 
            by = c("word1", "word2"), 
            suffix = c("_cvcl", "_imagenetvit")) %>%
  # Join with saycamvit similarities  
  left_join(saycamvit_similarities, 
            by = c("word1", "word2")) %>%
  rename(image_similarity_saycamvit = image_similarity) %>%
  # Join with openclip epoch 32
  left_join(openclip_similarities %>% filter(epoch == 32), 
            by = c("word1", "word2")) %>%
  rename(image_similarity_openclip32 = image_similarity) %>%
  # Join with openclip epoch 3
  left_join(openclip_similarities %>% filter(epoch == 3), 
            by = c("word1", "word2")) %>%
  rename(image_similarity_openclip3 = image_similarity) %>%
  
  # Join with trial metadata to get saliency diff and clip_sim
  left_join(trial_metadata %>% 
            distinct(Trials.imagePair, .keep_all = TRUE) %>%
            # Create word1/word2 columns from target/distractor images
            mutate(word1 = Trials.targetImage,
                   word2 = Trials.distractorImage) %>%
            select(word1, word2, MeanSaliencyDiff, image_similarity),
            by = c("word1", "word2")) %>%
  rename(clip_sim = image_similarity) %>%
  distinct(word1, word2, .keep_all = TRUE) |>
  # Select and rename the six variables for visualization
  select(
    cvcl_sim = image_similarity_cvcl,
    imagenetvit_sim = image_similarity_imagenetvit, 
    saycamvit_sim = image_similarity_saycamvit,
    openclip_epoch3_sim = image_similarity_openclip3,
    openclip_epoch32_sim = image_similarity_openclip32,
    mean_saliency_diff = MeanSaliencyDiff,
    clip_sim = clip_sim,
    word1 = word1, 
    word2 = word2
  ) 

ggplot(combined_data, aes(x=clip_sim, y=openclip_epoch3_sim)) +
  geom_point(size=3)+
  ggrepel::geom_label_repel(aes(label=paste0(word1, "-", word2))) +
  ggpubr::stat_cor() +
  geom_smooth(method="lm")
Warning: Removed 8 rows containing non-finite outside the scale range
(`stat_cor()`).
`geom_smooth()` using formula = 'y ~ x'
Warning: Removed 8 rows containing non-finite outside the scale range
(`stat_smooth()`).
Warning: Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Warning: Removed 8 rows containing missing values or values outside the scale range
(`geom_label_repel()`).

Correlation matrices

# Create ggpairs plot
similarity_plot <- ggpairs(
  combined_data,
  columns = c("cvcl_sim", "imagenetvit_sim", "saycamvit_sim", 
              "openclip_epoch3_sim", "openclip_epoch32_sim", 
              "mean_saliency_diff", "clip_sim"),
  title = "Correlations Between Different Similarity Measures")
ggsave("similarity_correlations.png", similarity_plot, 
       width = 16, height = 12, dpi = 300, bg = "white")
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning: Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Warning: Removed 8 rows containing non-finite outside the scale range
(`stat_density()`).
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning: Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Warning: Removed 8 rows containing non-finite outside the scale range
(`stat_density()`).
# Display the plot
print(similarity_plot)
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning: Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Warning: Removed 8 rows containing non-finite outside the scale range
(`stat_density()`).
Warning in ggally_statistic(data = data, mapping = mapping, na.rm = na.rm, :
Removed 8 rows containing missing values
Warning: Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 8 rows containing missing values or values outside the scale range
(`geom_point()`).
Warning: Removed 8 rows containing non-finite outside the scale range
(`stat_density()`).

# Print correlation matrix
cat("Correlation matrix:\n")
Correlation matrix:
cor_matrix <- cor(combined_data |> select(-word1, -word2), use = "complete.obs")
print(round(cor_matrix, 3))
                     cvcl_sim imagenetvit_sim saycamvit_sim openclip_epoch3_sim
cvcl_sim                1.000           0.775         0.791               0.599
imagenetvit_sim         0.775           1.000         0.919               0.401
saycamvit_sim           0.791           0.919         1.000               0.560
openclip_epoch3_sim     0.599           0.401         0.560               1.000
openclip_epoch32_sim    0.299           0.386         0.499               0.864
mean_saliency_diff      0.551           0.563         0.703               0.288
clip_sim                0.539           0.460         0.517               0.849
                     openclip_epoch32_sim mean_saliency_diff clip_sim
cvcl_sim                            0.299              0.551    0.539
imagenetvit_sim                     0.386              0.563    0.460
saycamvit_sim                       0.499              0.703    0.517
openclip_epoch3_sim                 0.864              0.288    0.849
openclip_epoch32_sim                1.000              0.050    0.858
mean_saliency_diff                  0.050              1.000    0.044
clip_sim                            0.858              0.044    1.000

DevBench

clip_data <- read.csv("clip_data.csv")
openclip_data <- read.csv("openclip.csv")
# Assuming looking_data_summarized is already loaded

# Prepare human data for compare_lwl function
# The compare_lwl function expects human data with 'prop' and 'age_bin' columns
human_data_lwl <- trialid_level_summary %>%
  arrange(Trials.targetImage, Trials.distractorImage) %>%
  mutate(prop = mean_value + 0.5) %>%
  mutate(trial = row_number()) %>%
  transmute(prop, age_bin="14-24", trial)  # Add other columns as needed

# Function to compare model with human data
compare_lwl <- function(model_data, human_data) {
  model_data_correct <- model_data %>% 
    mutate(correct = image1 > image2)
  
  human_data_nested <- human_data %>% 
    rename(image1 = prop) %>% 
    mutate(image2 = 1 - image1) %>% 
    nest(data = -age_bin) |> 
    mutate(opt_kl = lapply(data, function(d) get_opt_kl(d, model_data_correct)),
           kl = sapply(opt_kl, function(r) r$objective),
           beta = sapply(opt_kl, function(r) r$solution),
           iters = sapply(opt_kl, function(r) r$iterations),
           accuracy = mean(model_data_correct$correct, na.rm = TRUE)) %>% 
    select(-opt_kl, -data)
  
  return(human_data_nested)
}

# Function to process model similarity data
process_model_data <- function(data, similarity_col, model_name, calc_kl=TRUE) {
  # Parse comma-separated similarity values
  similarity_pairs <- strsplit(data[[similarity_col]], ",")
  
  # Convert to numeric and create data frame
  res <- data.frame(
    image1 = sapply(similarity_pairs, function(x) as.numeric(trimws(x[1]))),
    image2 = sapply(similarity_pairs, function(x) as.numeric(trimws(x[2]))),
    trial = seq_along(similarity_pairs)
  )
  
  # Calculate accuracy (assuming image1 corresponds to target, image2 to distractor)
  acc <- res %>%
    mutate(correct = image1 > image2) %>%
    summarise(accuracy = mean(correct, na.rm = TRUE)) %>%
    pull(accuracy)
  if (calc_kl) {
      # Compare with human data
  kls <- compare_lwl(res, human_data_lwl) %>% 
    mutate(model = model_name,
           accuracy = acc)
  
  return(kls)
  } else {
    return (res)
  }

}

clip_results <- process_model_data(clip_data |> arrange(target, distractor) |> mutate(trial=row_number()), "multimodal_similarity", "CLIP")
clip_results
# A tibble: 1 × 6
  age_bin      kl   beta iters accuracy model
  <chr>     <dbl>  <dbl> <int>    <dbl> <chr>
1 14-24   0.00937 0.0251    75    0.969 CLIP 

openclip

openclip_data <- read.csv("openclip.csv")

openclip_div_lwl <- openclip_data %>%
    group_by(epoch) %>%
    group_split() %>%
    map_dfr(function(epoch_data) {
      current_epoch <- unique(epoch_data$epoch)
      
      # Process this epoch's data
      epoch_results <- process_model_data(epoch_data |> arrange(word1, word2), "multimodal_similarity", paste0("OpenCLIP_epoch_", current_epoch))
      
      # Add epoch information
      epoch_results %>%
        mutate(epoch = current_epoch)
    })


lwl_oc <- ggplot(openclip_div_lwl |> rowwise() |> mutate(epoch = unique_checkpoints[[epoch]]), 
                 aes(x = log(epoch), y = kl, col = as.factor(age_bin))) +
  theme_classic() +
  geom_point() +
  geom_smooth(span = 1) +#, method = "lm") +
  # scale_colour_continuous() +
  # theme_classic() +
  labs(x = "log(Epoch)",
       y = TeX("Model–human dissimilarity ($D^*_{KL}$)"),
       col = "Age") +
  guides(colour = guide_legend(position = "inside")) +
  coord_cartesian(ylim = c(0, 0.02)) +
  theme(legend.position.inside = c(0.9, 0.8)) 
lwl_oc
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'

compare_lwl(human_data_lwl |> mutate(image1=prop, image2=1-prop), human_data_lwl)
# A tibble: 1 × 5
  age_bin         kl  beta iters accuracy
  <chr>        <dbl> <dbl> <int>    <dbl>
1 14-24   0.00000474  2.03    29    0.719

Quantifying multimodal accuracy?

multimodal_sim_vals <- cbind(trialid_level_summary |> arrange(Trials.targetImage, Trials.distractorImage), softmax_images(process_model_data(clip_data, "multimodal_similarity", "CLIP", calc_kl = FALSE), clip_results$beta) |> transmute(mm_acc = image1))

ggplot(multimodal_sim_vals |> filter(mm_acc > 0.5), aes(x=mm_acc, y=mean_value+0.5)) +
  theme_classic() +
  geom_point() +
  geom_smooth(method="lm") +
  ggpubr::stat_cor()
`geom_smooth()` using formula = 'y ~ x'

looking_data_summarized <- looking_data_summarized |> left_join(multimodal_sim_vals |> select(Trials.targetImage, Trials.distractorImage, mm_acc))
Joining with `by = join_by(Trials.targetImage, Trials.distractorImage)`

Thinking more about this, this is the wrong way of going about this since the beta is optimizing KL divergence and not accuracy per se.

mm_acc_model <- lmer(
    scale(corrected_target_looking+0.5) ~ 
      scale(mm_acc) * scale(age_in_months) + 
      scale(AoA_Est_target) + 
      scale(MeanSaliencyDiff) +
      (1 | SubjectInfo.subjID) +
      (1 | Trials.targetImage),
    data = looking_data_summarized |> 
      mutate(age_in_months = SubjectInfo.testAge / 30)
  )
summary(mm_acc_model)
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: 
scale(corrected_target_looking + 0.5) ~ scale(mm_acc) * scale(age_in_months) +  
    scale(AoA_Est_target) + scale(MeanSaliencyDiff) + (1 | SubjectInfo.subjID) +  
    (1 | Trials.targetImage)
   Data: 
mutate(looking_data_summarized, age_in_months = SubjectInfo.testAge/30)

REML criterion at convergence: 7013.1

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.85595 -0.62309 -0.01738  0.66754  2.82580 

Random effects:
 Groups             Name        Variance Std.Dev.
 SubjectInfo.subjID (Intercept) 0.017056 0.13060 
 Trials.targetImage (Intercept) 0.009987 0.09994 
 Residual                       0.963154 0.98140 
Number of obs: 2476, groups:  SubjectInfo.subjID, 91; Trials.targetImage, 24

Fixed effects:
                                     Estimate Std. Error         df t value
(Intercept)                        -8.531e-03  3.197e-02  2.618e+01  -0.267
scale(mm_acc)                      -1.052e-02  2.444e-02  8.285e+01  -0.430
scale(age_in_months)                5.779e-02  2.403e-02  9.108e+01   2.404
scale(AoA_Est_target)              -9.450e-02  2.829e-02  2.145e+01  -3.340
scale(MeanSaliencyDiff)             1.616e-03  2.657e-02  3.617e+01   0.061
scale(mm_acc):scale(age_in_months)  2.229e-02  1.974e-02  2.388e+03   1.129
                                   Pr(>|t|)   
(Intercept)                         0.79169   
scale(mm_acc)                       0.66804   
scale(age_in_months)                0.01823 * 
scale(AoA_Est_target)               0.00304 **
scale(MeanSaliencyDiff)             0.95182   
scale(mm_acc):scale(age_in_months)  0.25889   
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) scl(_) sc(__) s(AA_E s(MSD)
scal(mm_cc) -0.018                            
scl(g_n_mn)  0.003 -0.001                     
scl(AA_Es_)  0.008  0.130  0.008              
scl(MnSlnD)  0.020  0.026 -0.009  0.030       
scl(_):(__)  0.001  0.007 -0.001  0.002  0.005

```