library(tidyverse)
library(lubridate)
library(ggthemes)
library(assertthat)
library(langcog) # for CIs
library(mirt)
library(ggpubr)
library(knitr)
library(dplyr)
library(here)
library(lme4)
library(lmerTest)
First load all metadata we’ll need for the items
test_corpus <- read_csv(here::here("item_generation/4_create_multi_AFC_stimuli/new_test.csv")) %>%
filter(!Word1 %in% c('honey','scrabble')) # manually excluded
test_clip_cor <- read_csv(here::here("item_generation/4_create_multi_AFC_stimuli/exp1_all_trials2023-04-11.csv")) %>%
select(Word1, Word2, trial_type, cor, AoA_Est_Word2, AoA_Est_Word1) %>%
rename(wordPairing = trial_type) %>%
rename(clip_cor = cor) %>%
filter(Word1 %in% unique(test_corpus$Word1)) %>%
filter(!Word1 %in% c('honey','scrabble')) # manually excluded for symmetry across blocks
Create data structure and merge that has clip co =1 for target selection
test_clip_cor_identity <- test_clip_cor %>%
select(Word1, AoA_Est_Word1) %>%
distinct(Word1, AoA_Est_Word1) %>%
mutate(Word2 = Word1) %>%
mutate(AoA_Est_Word2 = AoA_Est_Word1) %>%
mutate(clip_cor = 1) %>%
mutate(wordPairing = 'target')
test_clip_cor <- test_clip_cor %>%
full_join(test_clip_cor_identity) %>%
arrange(Word1)
Rename meta adata structures so that they merge with trial data
clip_to_join <- test_clip_cor %>%
rename(targetWord = Word1) %>%
rename(answerWord = Word2)
test_corpus <- test_corpus %>%
left_join(test_clip_cor)
sum(is.na(test_corpus$clip_cor))
## [1] 0
Read in bing data
bing = read_csv(here::here('data/validation_data/preprocessed_data/all_bing_data_for_item_info.csv')) %>%
mutate(age_group = as.numeric(floor(age_in_months/12))) %>%
select(-block)
Read in multi-afc adult data Set age to “25” so we can plot it together
adults <- read_csv(here::here("data/data_multiafc/multi-afc-april28.csv")) %>%
mutate(age_group = as.numeric(25)) %>% # set
select(-block)
Make trial-levels data structure across all ages
trial_data <- read_csv(here::here("data/rocketship_data/multi-afc-with-meta.csv")) %>%
select(-block) %>%
mutate(age_group = as.numeric(age_group)) %>%
filter(age_group<12) %>% # older kids just don't have enough trials
full_join(bing) %>% # 3-5 year olds
full_join(adults) %>%
filter(studyId %in% c("school-multiAFC", "prolific-multiAFC", "validation-multiAFC"), completed == TRUE, task =="test_response") %>%
mutate(schoolId = ifelse(studyId == "school-multiAFC", "school", schoolId)) %>%
mutate(options_clean = str_replace_all(options, "'", "\"")) # necessary for json parsing
Create functions for parsing options from nested json
parse_json_column <- function(json_col) {
map(json_col, ~as.data.frame(t(unlist(jsonlite::fromJSON(.x, flatten = TRUE)))))
}
Parse out all of the target/distractor pairing
parsed_list <- parse_json_column(trial_data$options_clean)
# Combine the list of data frames into a single data frame
parsed_df <- bind_rows(parsed_list)
# Combine the parsed columns with the original data frame
final_df <- cbind(trial_data %>% select(-options), parsed_df)
Join together and clean datastructures
df.multiAFC.trials <- final_df %>%
pivot_longer(cols = c("0", "1", "2", "3")) %>%
filter(answerWord == value) %>%
select(pid, runId, schoolId, answerWord, targetWord, numAFC, value, correct, rt, age_group) %>%
left_join(test_corpus %>%
rename(targetWord = "Word1", answerWord = "Word2")) %>%
filter(is.na(itemGroup) | (itemGroup == "test")) %>%
mutate(wordPairing = ifelse(is.na(wordPairing), "target", wordPairing)) %>%
filter(!targetWord %in% c("ant","ball","bear","scrabble","honey")) %>%
select(pid, runId, schoolId, answerWord, targetWord, numAFC, value, correct, rt, wordPairing, age_group, clip_cor)
Create age/group and number of participants for filtering for plots / age-based analyses
age_group_pid <- df.multiAFC.trials %>%
group_by(age_group) %>%
summarize(num_pids = length(unique(pid)))
We have 1446 children aged 3-11 years, and 205 adults.
Now write code to fill out response pattern for all possible target/distractor responses on each age for each age…for use later.
full_item_structure_4AFC <- clip_to_join %>%
mutate(numAFC = 4)
full_item_structure_3AFC <- clip_to_join %>%
filter(wordPairing != 'distal') %>%
mutate(numAFC = 3)
full_item_structure_2AFC <- clip_to_join %>%
filter(!wordPairing %in% c('distal','easy')) %>%
mutate(numAFC = 2)
full_item_structure <- full_item_structure_4AFC %>%
full_join(full_item_structure_3AFC) %>%
full_join(full_item_structure_2AFC)
Now fill this out by all ages in the dataset, oof this was annoying
full_item_structure_by_age = map_df(age_group_pid$age_group, ~full_item_structure %>% mutate(age_group = .x))
First do this across all age groups – for basic item plots.
df.multiAFC.totalAttempts <- df.multiAFC.trials %>%
group_by(targetWord, numAFC) %>%
tally() %>%
dplyr::rename(totalAttempts = n)
df.multiAFC.distractor.summary <- df.multiAFC.trials %>%
group_by(targetWord, answerWord, numAFC, wordPairing) %>%
tally() %>%
left_join(df.multiAFC.totalAttempts) %>%
mutate(perc = n/totalAttempts) %>%
full_join(full_item_structure) %>% # need to fill out item structure (but not by age)
mutate(perc = replace_na(perc, replace=0))
Look at histogram of how often an item was attempted overal
hist(df.multiAFC.totalAttempts$totalAttempts)
Look at histograms of how often teh target was chosen
target_pc <- df.multiAFC.distractor.summary %>%
filter(wordPairing=='target')
hist(target_pc$perc)
Low accuracy items
target_pc %>%
filter(perc<.5) %>%
kable()
| targetWord | answerWord | numAFC | wordPairing | n | totalAttempts | perc | clip_cor | AoA_Est_Word2 | AoA_Est_Word1 |
|---|---|---|---|---|---|---|---|---|---|
| bobsled | bobsled | 3 | target | 117 | 292 | 0.4006849 | 1 | 9.38 | 9.38 |
| bobsled | bobsled | 4 | target | 111 | 278 | 0.3992806 | 1 | 9.38 | 9.38 |
| candlestick | candlestick | 2 | target | 98 | 254 | 0.3858268 | 1 | 5.61 | 5.61 |
| candlestick | candlestick | 3 | target | 118 | 275 | 0.4290909 | 1 | 5.61 | 5.61 |
| candlestick | candlestick | 4 | target | 128 | 293 | 0.4368601 | 1 | 5.61 | 5.61 |
| grate | grate | 2 | target | 138 | 284 | 0.4859155 | 1 | 8.90 | 8.90 |
| grate | grate | 4 | target | 125 | 257 | 0.4863813 | 1 | 8.90 | 8.90 |
| mulch | mulch | 2 | target | 111 | 281 | 0.3950178 | 1 | 9.22 | 9.22 |
| mulch | mulch | 3 | target | 112 | 284 | 0.3943662 | 1 | 9.22 | 9.22 |
| mulch | mulch | 4 | target | 100 | 259 | 0.3861004 | 1 | 9.22 | 9.22 |
High accuracy items
target_pc %>%
filter(perc>.95) %>%
kable()
| targetWord | answerWord | numAFC | wordPairing | n | totalAttempts | perc | clip_cor | AoA_Est_Word2 | AoA_Est_Word1 |
|---|---|---|---|---|---|---|---|---|---|
| acorn | acorn | 2 | target | 243 | 254 | 0.9566929 | 1 | 5.95 | 5.95 |
| cake | cake | 2 | target | 262 | 273 | 0.9597070 | 1 | 3.26 | 3.26 |
| carrot | carrot | 2 | target | 283 | 288 | 0.9826389 | 1 | 2.74 | 2.74 |
| elbow | elbow | 2 | target | 286 | 300 | 0.9533333 | 1 | 4.78 | 4.78 |
| fan | fan | 2 | target | 265 | 277 | 0.9566787 | 1 | 5.63 | 5.63 |
| footbath | footbath | 2 | target | 277 | 286 | 0.9685315 | 1 | 8.53 | 8.53 |
| footbath | footbath | 4 | target | 253 | 266 | 0.9511278 | 1 | 8.53 | 8.53 |
| hamster | hamster | 2 | target | 261 | 272 | 0.9595588 | 1 | 4.37 | 4.37 |
| hedgehog | hedgehog | 2 | target | 257 | 267 | 0.9625468 | 1 | 8.63 | 8.63 |
| lollipop | lollipop | 2 | target | 248 | 255 | 0.9725490 | 1 | 3.89 | 3.89 |
| lollipop | lollipop | 3 | target | 269 | 281 | 0.9572954 | 1 | 3.89 | 3.89 |
| map | map | 2 | target | 250 | 261 | 0.9578544 | 1 | 5.60 | 5.60 |
| map | map | 3 | target | 213 | 224 | 0.9508929 | 1 | 5.60 | 5.60 |
| marshmallow | marshmallow | 2 | target | 254 | 263 | 0.9657795 | 1 | 3.80 | 3.80 |
| pie | pie | 2 | target | 284 | 292 | 0.9726027 | 1 | 3.67 | 3.67 |
| potato | potato | 2 | target | 243 | 253 | 0.9604743 | 1 | 4.84 | 4.84 |
| rice | rice | 2 | target | 257 | 266 | 0.9661654 | 1 | 3.72 | 3.72 |
| rice | rice | 4 | target | 263 | 273 | 0.9633700 | 1 | 3.72 | 3.72 |
| shower | shower | 2 | target | 263 | 275 | 0.9563636 | 1 | 4.72 | 4.72 |
| shower | shower | 3 | target | 238 | 249 | 0.9558233 | 1 | 4.72 | 4.72 |
| squirrel | squirrel | 2 | target | 249 | 259 | 0.9613900 | 1 | 4.44 | 4.44 |
| sunflower | sunflower | 2 | target | 240 | 246 | 0.9756098 | 1 | 6.00 | 6.00 |
| sunflower | sunflower | 3 | target | 238 | 245 | 0.9714286 | 1 | 6.00 | 6.00 |
| sunflower | sunflower | 4 | target | 268 | 277 | 0.9675090 | 1 | 6.00 | 6.00 |
| turkey | turkey | 2 | target | 257 | 264 | 0.9734848 | 1 | 3.95 | 3.95 |
| watermelon | watermelon | 2 | target | 235 | 245 | 0.9591837 | 1 | 4.22 | 4.22 |
| watermelon | watermelon | 3 | target | 253 | 264 | 0.9583333 | 1 | 4.22 | 4.22 |
| watermelon | watermelon | 4 | target | 265 | 271 | 0.9778598 | 1 | 4.22 | 4.22 |
df.multiAFC.totalAttempts.byAge <- df.multiAFC.trials %>%
group_by(targetWord, numAFC, age_group) %>%
tally() %>%
dplyr::rename(totalAttempts = n)
df.multiAFC.distractor.summary.byAge <- df.multiAFC.trials %>%
group_by(targetWord, answerWord, numAFC, wordPairing, age_group) %>%
tally() %>%
left_join(df.multiAFC.totalAttempts.byAge) %>%
mutate(perc = n/totalAttempts) %>%
full_join(full_item_structure_by_age)
couple of sanity checks for missing values that shuold be zero
assert_that(sum(is.na(df.multiAFC.distractor.summary.byAge$age_group))==0)
## [1] TRUE
assert_that(sum(is.na(df.multiAFC.distractor.summary.byAge$clip_cor))==0)
## [1] TRUE
There are some missing observations because these items were never chosen on a given trial/age group – need to fill those out
But we also have some target/distractor/age group pairings that are VERY sparsely populated – that makes sense especially as kids get more accurate and don’t choose distal items. We also have relatively few younger kids.
hist(df.multiAFC.distractor.summary.byAge$totalAttempts)
None of these are actually zero, just hcekcing.
sum(df.multiAFC.distractor.summary.byAge$totalAttempts==0, na.rm=TRUE)
## [1] 0
We need to populate “0” value in the percent chosen category for these distractor items that were never chosen and so not in the data structure
sum(is.na(df.multiAFC.distractor.summary.byAge$perc))
## [1] 2502
df.multiAFC.distractor.summary.byAge <- df.multiAFC.distractor.summary.byAge %>%
mutate(perc = replace_na(perc, replace=0))
Summary by distractor type and numAFC by age, group, use confidence intervals here (langcog package)
df.multiAFC.distractor.summary.perc <- df.multiAFC.distractor.summary.byAge %>%
ungroup() %>%
mutate(wordPairing = factor(wordPairing, levels = c('target','hard','easy','distal'), labels = c("Target word", "High sim. dist", "Med sim. dist.", "Low sim. dist."))) %>% # just reordering
group_by(age_group, numAFC, wordPairing) %>%
multi_boot_standard(col = 'perc') %>% # compute CIs
mutate(number_afc = as.factor(paste0(numAFC,' AFC'))) #cosmetic for plots
First visualize within each age group – can plots nAFCs by different colors
Looks like gradient is getting sharper with age, as expected
ggplot(data = df.multiAFC.distractor.summary.perc, aes(x=wordPairing, y=mean, col=number_afc)) +
geom_point() +
geom_linerange(aes(y=mean, ymax = ci_upper, ymin = ci_lower)) +
geom_line(aes(group=number_afc)) +
theme(aspect.ratio=.75) +
theme_few() +
ylab('Proportion images chosen') +
facet_wrap(~age_group,nrow=2) +
theme(axis.text.x = element_text(size=6, angle = 45, hjust = 1))
Now plot within afc, with age groups as colors Make adults grey here (lots of extra code) so that the kid age gradient isn’t too compressed
ggplot(data = df.multiAFC.distractor.summary.perc %>% filter(age_group<25), aes(x=wordPairing, y=mean, col=age_group)) +
geom_point(alpha=.8) +
geom_point(data = df.multiAFC.distractor.summary.perc %>% filter(age_group==25), color='grey', alpha=.8) +
geom_linerange(aes(y=mean, ymax = ci_upper, ymin = ci_lower), alpha=.3) +
geom_linerange(data = df.multiAFC.distractor.summary.perc %>% filter(age_group==25), aes(y=mean, ymax = ci_upper, ymin = ci_lower), alpha=.3, color='grey') +
geom_line(data = df.multiAFC.distractor.summary.perc %>% filter(age_group==25), color='grey',aes(group=age_group)) +
geom_line(aes(group=age_group)) +
facet_wrap(~number_afc) +
theme(aspect.ratio=.75) +
scale_color_viridis_c(name='Age (in years)') +
xlab('') +
ylab('Proportion chosen') +
theme_few() +
ylim(0,1) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
ggsave(filename = 'figures/prop_chosen_by_type_by_age.pdf', width=6, height=5, units='in')
Let’s try binning by clip similarity instead of wordPairing – look similar
clip_cor_distractors <- df.multiAFC.distractor.summary.byAge %>%
ungroup() %>%
mutate(clip_quantile = ntile(clip_cor, 4)) %>%
group_by(age_group, numAFC, clip_quantile) %>%
multi_boot_standard(col = 'perc')
ggplot(data = clip_cor_distractors %>% filter(age_group<25), aes(x=clip_quantile, y=mean, col=age_group)) +
geom_point(alpha=.8) +
geom_point(data = clip_cor_distractors %>% filter(age_group==25), color='grey', alpha=.8) +
geom_linerange(aes(y=mean, ymax = ci_upper, ymin = ci_lower), alpha=.3) +
geom_linerange(data = clip_cor_distractors %>% filter(age_group==25), aes(y=mean, ymax = ci_upper, ymin = ci_lower), alpha=.3, color='grey') +
geom_line(data = clip_cor_distractors %>% filter(age_group==25), color='grey',aes(group=age_group)) +
geom_line(aes(group=age_group)) +
facet_wrap(~numAFC) +
theme(aspect.ratio=.75) +
scale_color_viridis_c(name='Age (in years)') +
xlab('CLIP similarity quantiles') +
ylab('Proportion chosen') +
theme_few() +
ylim(0,1) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
# ggsave(filename = 'figures/prop_chosen_by_type_by_age.pdf', width=6, height=5, units='in')
Third way of visaulizing this data – which kinds of items were chosen as a function of age in each NAFC
ggplot(data = df.multiAFC.distractor.summary.perc, aes(x=age_group, y=mean, col=wordPairing)) +
geom_point() +
geom_linerange(aes(y=mean, ymax = ci_upper, ymin = ci_lower), alpha=.8) +
geom_smooth(aes(group=wordPairing), span=10) +
facet_wrap(~number_afc) +
theme(aspect.ratio=.75) +
# scale_color_viridis_c(name='Age (in years)') +
xlab('') +
ylab('Proportion chosen') +
theme_few() +
ylim(0,1) +
ggtitle('Proportion images chosen by # of distractors')
ggsave(filename = 'figures/prop_chosen_by_age_by_type.pdf', width=5, height=5, units='in')
First in order over all kids
df.multiAFC.distractor.summary.word <- df.multiAFC.distractor.summary %>%
group_by(numAFC, wordPairing, targetWord) %>%
summarise(percentile = mean(perc)) %>% # averaging across age
arrange(targetWord, numAFC) %>%
ungroup() %>%
mutate(targetWord = fct_reorder(targetWord, percentile))
We have a big range of item effects here – some near ceiling, some near floor, and we span the whole range
ggplot(data = df.multiAFC.distractor.summary.word, aes(x=targetWord, y=percentile, color=wordPairing)) +
geom_point(alpha=.8, size=2) +
theme_few(base_size=12) +
theme(axis.text.x = element_text(size = 4, angle = 90, hjust = 1)) +
facet_wrap(~numAFC, nrow=3) +
theme(legend.position = 'bottom')
ggsave(filename = 'figures/item_effects.pdf', width=5, height=5, units='in')
Plotting individual item effects for the younger age groups is tricky…just not enough data from the youngest/oldest kids.
Plot which items do vs. don’t follow the expected gradient, from Anya’s prior analysis
df.flag.pair <- df.multiAFC.distractor.summary.word %>%
pivot_wider(names_from = wordPairing, values_from = percentile) %>%
filter((hard > target) | (easy > hard) | (easy > target)) %>%
add_column(flag = 1) %>%
select(numAFC, targetWord, flag)
df.multiAFC.distractor.summary.word.updated <- df.multiAFC.distractor.summary.word %>%
left_join(df.flag.pair) %>%
mutate(flag = ifelse(is.na(flag), "expected", "flag"))
Lets look also at which items were hardest
hardest_items <- df.multiAFC.distractor.summary.word %>%
group_by(targetWord) %>%
filter(wordPairing=='target') %>%
filter(numAFC==4) %>%
arrange(percentile) %>%
ungroup() %>%
slice_max(order_by=-percentile, n=20)
Red items are the ones that don’t follow the gradient – note that the axes is reversed here
ggplot(data=df.multiAFC.distractor.summary.word.updated , aes(x=wordPairing, y=percentile)) +
geom_point(size = 1, alpha = 0.6) +
geom_line(aes(group = targetWord, color = flag), alpha=.4) +
facet_wrap(~numAFC) +
scale_color_manual(values=c( "#8F993E", "#E05A1D")) +
labs(x = "item distractor type",
y = "percentage of responses")
Let’s manually inspect these items that are off the gradient
Make wide data structure for 4afc only
df.multiAFC.distractor.summary.wide <- df.multiAFC.distractor.summary %>%
ungroup() %>%
filter(numAFC == 4) %>%
select(-c(n, answerWord, clip_cor,AoA_Est_Word1, AoA_Est_Word2)) %>%
pivot_wider(names_from = wordPairing, values_from = perc)
Look at specific items that are off (hard chosen less often than easy distractor)
df.multiAFC.distractor.summary.wide %>%
filter((hard + 0.1)< easy)
## # A tibble: 3 × 7
## targetWord numAFC totalAttempts target distal hard easy
## <chr> <dbl> <int> <dbl> <dbl> <dbl> <dbl>
## 1 aloe 4 298 0.540 0.111 0.0906 0.258
## 2 coaster 4 252 0.591 0.0913 0.0437 0.274
## 3 cymbal 4 276 0.638 0.0435 0.0797 0.239
Look at specific items that are off for 3afc (hard chosen less often than easy distractor)
df.multiAFC.distractor.summary %>%
ungroup() %>%
filter(numAFC == 3) %>%
select(-c(n, answerWord, clip_cor,AoA_Est_Word1, AoA_Est_Word2)) %>%
pivot_wider(names_from = wordPairing, values_from = perc) %>%
filter((hard + 0.1)< easy)
## # A tibble: 4 × 6
## targetWord numAFC totalAttempts target hard easy
## <chr> <dbl> <int> <dbl> <dbl> <dbl>
## 1 aloe 3 279 0.642 0.0609 0.297
## 2 coaster 3 275 0.575 0.0836 0.342
## 3 cymbal 3 297 0.690 0.0640 0.246
## 4 omelet 3 267 0.749 0.0637 0.187
Look at specific items that are off for 2afc (hard chosen less often than easy distractor)
df.multiAFC.distractor.summary %>%
ungroup() %>%
filter(numAFC == 2) %>%
select(-c(n, answerWord, clip_cor,AoA_Est_Word1, AoA_Est_Word2)) %>%
pivot_wider(names_from = wordPairing, values_from = perc) %>%
arrange(target)
## # A tibble: 108 × 5
## targetWord numAFC totalAttempts target hard
## <chr> <dbl> <int> <dbl> <dbl>
## 1 candlestick 2 254 0.386 0.614
## 2 mulch 2 281 0.395 0.605
## 3 grate 2 284 0.486 0.514
## 4 bobsled 2 259 0.533 0.467
## 5 cheese 2 282 0.596 0.404
## 6 freezer 2 245 0.604 0.396
## 7 thermos 2 296 0.608 0.392
## 8 sauerkraut 2 261 0.636 0.364
## 9 tuxedo 2 261 0.640 0.360
## 10 corset 2 266 0.647 0.353
## # ℹ 98 more rows
Can clearly see some of the data sparsity issues here at the younger ages
ggplot(data=df.multiAFC.distractor.summary.byAge %>% filter(wordPairing != 'target'), aes(x=clip_cor, y=perc)) +
geom_point(alpha=.05) +
geom_smooth(method='lm') +
theme_few() +
ylab('Prop distractor chosen') +
xlab('Similarity in CLIP space') +
facet_grid(~age_group) +
theme(axis.text.x = element_text(size=6)) +
scale_x_continuous(breaks = c(.5, .7, .9)) +
stat_cor(method = "pearson", aes(label = ..r.label..), size=2) +
ylim(0,1)
clip_correlation_by_age_by_afc <- df.multiAFC.distractor.summary.byAge %>%
filter(wordPairing != 'target') %>%
group_by(age_group) %>%
summarize(rvalue = cor(clip_cor, perc))
Correlation between adults/kids oddly isn’t that high, maybe some data sparisty here
# this is 962 possibilities across all 108 items x 234afc combinations
adult_patterns <- df.multiAFC.distractor.summary.byAge %>%
filter(age_group==25)
Just doing within all afc because grouping is tricky but oddly not that high relative to clip correlations, adult data does correlate with itself at r=1 within the pipe suggesting no indexing issues
adult_correlation_by_age <- df.multiAFC.distractor.summary.byAge %>%
group_by(age_group) %>%
summarize(r_adults = cor(perc, adult_patterns$perc))
Here’s a take home plot that’s easy – CLIP- behavior plot across age on error patterns
Have to think about how much of this is about reliability, ugh.
ggplot(data = clip_correlation_by_age_by_afc, aes(x=age_group, y=rvalue)) +
geom_point() +
geom_smooth(span=1, alpha=.2) +
theme_few() +
ylab('CLIP-Behavior correlation') +
xlab('Age in years')
s
Modeling image choice data for a given trial - what we’re modeling is the proportion of times an image was chosen on a trial by children in a certain age group, as a function of the (1) clip correlation between the target word and the distractor word, (clip_cor) (2) the age in years of the children/adults participating (age_group) (3) the number of other distrators on that trial (numAFC)
With random effects for (1) Random slopes of the clip_correlation on each targetWord, (2) the number of times this trial was seen by kids in this age group, which varies a lot given the different recruitment strategies and we want to account for this
We’re looking for way to test for that gradient items children choose as a function of CLIP simialrity
Add numafc as a covariate and random effects for the clip | target_word
summary(lmer(data = df.multiAFC.distractor.summary.byAge, perc ~ clip_cor*age_group + numAFC + (clip_cor|targetWord) + (1|totalAttempts)))
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: perc ~ clip_cor * age_group + numAFC + (clip_cor | targetWord) +
## (1 | totalAttempts)
## Data: df.multiAFC.distractor.summary.byAge
##
## REML criterion at convergence: -2697.3
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -2.6979 -0.7537 0.0265 0.7530 4.5649
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## targetWord (Intercept) 0.679090 0.82407
## clip_cor 0.827115 0.90946 -1.00
## totalAttempts (Intercept) 0.004081 0.06388
## Residual 0.036562 0.19121
## Number of obs: 7218, groups: targetWord, 108; totalAttempts, 86
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) -6.206e-01 8.763e-02 1.406e+02 -7.082 6.24e-11 ***
## clip_cor 1.177e+00 9.521e-02 1.327e+02 12.365 < 2e-16 ***
## age_group -1.040e-01 3.101e-03 6.998e+03 -33.528 < 2e-16 ***
## numAFC -5.117e-03 2.994e-03 6.989e+03 -1.709 0.0875 .
## clip_cor:age_group 1.183e-01 3.406e-03 6.962e+03 34.737 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) clp_cr ag_grp numAFC
## clip_cor -0.986
## age_group -0.318 0.312
## numAFC -0.169 0.065 0.009
## clp_cr:g_gr 0.313 -0.321 -0.982 -0.008
Add in estimated aoa of word1/2 as covariates, same results – still see interaction
summary(lmer(data = df.multiAFC.distractor.summary.byAge, perc ~ clip_cor*age_group + numAFC + AoA_Est_Word2 + AoA_Est_Word1 + (clip_cor|targetWord) + (1|totalAttempts)))
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula:
## perc ~ clip_cor * age_group + numAFC + AoA_Est_Word2 + AoA_Est_Word1 +
## (clip_cor | targetWord) + (1 | totalAttempts)
## Data: df.multiAFC.distractor.summary.byAge
##
## REML criterion at convergence: -2983
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -2.8656 -0.7194 0.0291 0.7229 4.4815
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## targetWord (Intercept) 0.808789 0.89933
## clip_cor 0.994509 0.99725 -1.00
## totalAttempts (Intercept) 0.003988 0.06315
## Residual 0.034852 0.18669
## Number of obs: 7218, groups: targetWord, 108; totalAttempts, 86
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) -6.693e-01 9.571e-02 1.401e+02 -6.992 1.01e-10 ***
## clip_cor 1.178e+00 1.027e-01 1.255e+02 11.462 < 2e-16 ***
## age_group -1.035e-01 3.028e-03 6.990e+03 -34.187 < 2e-16 ***
## numAFC -9.288e-03 2.933e-03 6.985e+03 -3.166 0.00155 **
## AoA_Est_Word2 4.401e-02 2.484e-03 6.025e+03 17.720 < 2e-16 ***
## AoA_Est_Word1 -3.431e-02 3.200e-03 3.107e+02 -10.721 < 2e-16 ***
## clip_cor:age_group 1.178e-01 3.326e-03 6.954e+03 35.430 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) clp_cr ag_grp numAFC AA_E_W2 AA_E_W1
## clip_cor -0.973
## age_group -0.285 0.282
## numAFC -0.153 0.059 0.008
## AA_Est_Wrd2 -0.009 -0.003 0.009 -0.082
## AA_Est_Wrd1 -0.137 0.015 -0.004 0.066 -0.674
## clp_cr:g_gr 0.280 -0.291 -0.982 -0.008 -0.009 0.002
But this goes away if we exclude kids choosing the target word – only looking at errors
We still see many effects of age and clip similarity, but no interaction
summary(lmer(data = df.multiAFC.distractor.summary.byAge %>% filter(wordPairing != 'target'), perc ~ clip_cor*age_group + numAFC + (clip_cor|targetWord) + (1|totalAttempts)))
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: perc ~ clip_cor * age_group + numAFC + (clip_cor | targetWord) +
## (1 | totalAttempts)
## Data: df.multiAFC.distractor.summary.byAge %>% filter(wordPairing !=
## "target")
##
## REML criterion at convergence: -5792.1
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -3.8065 -0.5349 -0.1407 0.3882 5.7615
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## targetWord (Intercept) 0.133263 0.3651
## clip_cor 0.286672 0.5354 -0.99
## totalAttempts (Intercept) 0.009721 0.0986
## Residual 0.011993 0.1095
## Number of obs: 4139, groups: targetWord, 108; totalAttempts, 85
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) -3.731e-03 5.327e-02 3.038e+02 -0.070 0.9442
## clip_cor 3.988e-01 6.799e-02 2.048e+02 5.865 1.78e-08 ***
## age_group -6.796e-03 3.153e-03 3.930e+03 -2.156 0.0312 *
## numAFC -2.265e-02 2.526e-03 3.937e+03 -8.967 < 2e-16 ***
## clip_cor:age_group -1.332e-03 3.893e-03 3.924e+03 -0.342 0.7323
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) clp_cr ag_grp numAFC
## clip_cor -0.951
## age_group -0.529 0.504
## numAFC -0.328 0.174 0.006
## clp_cr:g_gr 0.524 -0.513 -0.989 -0.006
What if we exclude the hardest items that the youngest kids don’t know? This is about half of the words 57/108 words – and we still see the interaction
summary(lmer(data = df.multiAFC.distractor.summary.byAge %>% filter(AoA_Est_Word1<8), perc ~ clip_cor*age_group + numAFC + (clip_cor|targetWord) + (1|totalAttempts)))
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: perc ~ clip_cor * age_group + numAFC + (clip_cor | targetWord) +
## (1 | totalAttempts)
## Data: df.multiAFC.distractor.summary.byAge %>% filter(AoA_Est_Word1 <
## 8)
##
## REML criterion at convergence: -1477.3
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -2.6965 -0.7820 0.0727 0.7526 4.5566
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## totalAttempts (Intercept) 0.003589 0.0599
## targetWord (Intercept) 0.567541 0.7534
## clip_cor 0.682392 0.8261 -1.00
## Residual 0.038239 0.1955
## Number of obs: 4537, groups: totalAttempts, 85; targetWord, 70
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) -1.006e+00 1.040e-01 1.029e+02 -9.677 3.92e-16 ***
## clip_cor 1.588e+00 1.120e-01 9.645e+01 14.172 < 2e-16 ***
## age_group -1.051e-01 4.501e-03 4.390e+03 -23.344 < 2e-16 ***
## numAFC 5.216e-03 3.870e-03 4.393e+03 1.348 0.178
## clip_cor:age_group 1.173e-01 4.894e-03 4.368e+03 23.968 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) clp_cr ag_grp numAFC
## clip_cor -0.986
## age_group -0.387 0.382
## numAFC -0.186 0.073 0.004
## clp_cr:g_gr 0.383 -0.391 -0.987 -0.006
Bizarrely the interaction is back for distractors (though p=.04) for these words with lower AoA ? Not sure why
summary(lmer(data = df.multiAFC.distractor.summary.byAge %>% filter(wordPairing != 'target' & numAFC>2), perc ~ clip_cor*age_group + numAFC + (clip_cor|targetWord) + (1|totalAttempts)))
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: perc ~ clip_cor * age_group + numAFC + (clip_cor | targetWord) +
## (1 | totalAttempts)
## Data: df.multiAFC.distractor.summary.byAge %>% filter(wordPairing !=
## "target" & numAFC > 2)
##
## REML criterion at convergence: -5000.8
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -3.4138 -0.5266 -0.1420 0.3545 6.0255
##
## Random effects:
## Groups Name Variance Std.Dev. Corr
## targetWord (Intercept) 0.12760 0.3572
## clip_cor 0.26537 0.5151 -0.99
## totalAttempts (Intercept) 0.01059 0.1029
## Residual 0.01033 0.1016
## Number of obs: 3312, groups: targetWord, 108; totalAttempts, 83
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) -5.826e-02 5.316e-02 3.175e+02 -1.096 0.2740
## clip_cor 3.913e-01 6.599e-02 2.020e+02 5.930 1.29e-08 ***
## age_group -6.615e-03 3.123e-03 3.086e+03 -2.118 0.0342 *
## numAFC -9.301e-03 3.818e-03 3.084e+03 -2.436 0.0149 *
## clip_cor:age_group -1.793e-04 3.905e-03 3.080e+03 -0.046 0.9634
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) clp_cr ag_grp numAFC
## clip_cor -0.928
## age_group -0.527 0.520
## numAFC -0.353 0.102 0.008
## clp_cr:g_gr 0.521 -0.532 -0.987 -0.009