library(tidyverse)
library(lubridate)
library(ggthemes)
# library(langcog)
library(mirt)
library(knitr)
library("tidyverse") # for data wrangling
library(dplyr)
library(ggpubr)
2afc data
school_data_2afc_cleaned <- read_csv(file = here::here('data/preprocessed_data/all_preprocessed_data.csv')) %>%
mutate(word1 = str_replace(word1, ' ', '.')) %>%
mutate(word2 = str_replace(word2, ' ', '.')) %>%
mutate(item_pair = paste0(word1,'_',word2)) %>%
mutate(numAFC = 2) %>%
mutate(sub_id = pid, numAFC = as.character(numAFC)) %>%
filter(cohort=='school') %>%
mutate(administration = 'initial')
We have 1606 rocketship children who completed the 2afc version.
Multiafc version
school_data_multi_afc <- read_csv(here::here('data/rocketship_data/multi-afc-with-meta.csv'))
We have 1476 rocketship (and 200 adults) who completed the multiafc version.
completed_twice_pids <- school_data_multi_afc %>%
filter(pid %in% unique(school_data_2afc_cleaned$pid) ) %>%
distinct(pid)
There are 904 kids who completed both versions.
Compute correct by pid
correct_by_pid <- school_data_2afc_cleaned %>%
group_by(pid) %>%
summarize(prop_correct_2afc = mean(correct))
Meta data for joining back
pid_by_age <- school_data_multi_afc %>%
distinct(pid, age_group, age_numeric)
Compute data structure with accuracy overall for repeat kids
correct_by_pid_by_multi_afc <- school_data_multi_afc %>%
mutate(age_group = as.numeric(age_group)) %>%
filter(itemType=='test') %>%
filter(completed==TRUE) %>%
group_by(pid, age_group) %>%
summarize(prop_correct_overall = mean(correct))
completed_twice_overall <- correct_by_pid %>% # 2afc initial
left_join(correct_by_pid_by_multi_afc, by=c('pid')) %>% # join overall multiafc
filter(!is.na(prop_correct_overall))
Make data structure now with multiafc
correct_by_pid_by_afc <- school_data_multi_afc %>%
filter(itemType=='test') %>%
filter(completed==TRUE) %>%
group_by(pid, numAFC, age_group) %>%
summarize(prop_correct_nafc = mean(correct))
Make it wider to join
correct_by_pid_by_afc_wide <- correct_by_pid_by_afc %>%
rowwise() %>%
mutate(numAFC_string = paste0('num',numAFC)) %>%
select(-numAFC) %>%
pivot_wider(names_from = numAFC_string, values_from = 'prop_correct_nafc')
Now join back with 2afc data, only get kids in both
completed_twice <- correct_by_pid %>%
left_join(correct_by_pid_by_afc) %>%
filter(!is.na(prop_correct_nafc))
Make a long version
completed_twice_long <- correct_by_pid %>%
left_join(correct_by_pid_by_afc_wide) %>%
filter(!is.na(num2))
ggplot(data=completed_twice_overall %>% mutate(age_group = as.numeric(age_group)), aes(x=prop_correct_2afc, y=prop_correct_overall, col=age_group)) +
geom_point(alpha=.7) +
geom_smooth() +
theme_few(base_size=12) +
ggtitle('Initial (2afc) vs. second (multiafc) visual vocab admin') +
ggpubr::stat_cor(size=3) +
xlab('Initial 2afc accuracy') +
ylab('Second multiaf accuracy (overall)') +
facet_wrap(~age_group)
Correlations between first and second administration aren’t that high.
ggplot(data=completed_twice, aes(x=prop_correct_2afc, y=prop_correct_nafc, col=as.numeric(age_group))) +
geom_point(alpha=.6) +
geom_smooth() +
theme_few(base_size=16) +
ggtitle('Initial vs. second adminstration by AFC') +
facet_wrap(~numAFC) +
xlab('Initial 2afc acc') +
ylab('Second multiafc acc') +
stat_cor() +
theme(legend.position='none')
Use preprocessed data from other children/adults who have taken any version to estimate thetas for each child.
Get all 2afc data
all_2afc_data <- read_csv(file = here::here('data/preprocessed_data/all_preprocessed_data.csv')) %>%
mutate(itemId = paste(word1, 2, sep = "_")) %>%
filter(cohort %in% c('school','prolific')) %>%
select(pid, itemId, correct)
All multiafc data - this includes the prolific data as well, they are marked as “adults”
all_multiafc_data <- school_data_multi_afc %>%
mutate(itemId = paste(targetWord, numAFC, sep = "_")) %>%
select(pid, itemId, correct)
Merge it back together
all_data <- all_multiafc_data %>%
full_join(all_2afc_data)
Make structure for IRT models
d_wide_all<- all_data %>%
ungroup() %>%
arrange(itemId) %>%
ungroup() %>%
pivot_wider(names_from=itemId, values_from=correct, values_fn = ~mean(.x)) %>%
ungroup()
d_mat_all <- d_wide_all %>%
select(-pid) %>%
data.frame %>%
data.matrix
rownames(d_mat_all) <- d_wide_all$sub_id
# d_mat_all <- processed_data_resp %>%
# select(-...1)
start.dim <- dim(d_mat_all)[2]
mm.string = (
'F = 1-%d,
PRIOR = (1-%d, a1, norm, 1, 0.5),
PRIOR = (1-%d, d, norm, 0, 2),
LBOUND = (1-%g, g, 0.25),
UBOUND = (1-%g, g, 1),
LBOUND = (1-%d, a1, 0.5)'
)
mm = mirt.model(sprintf(mm.string,
start.dim,
start.dim,
start.dim,
start.dim,
start.dim,
start.dim))
Run basic 3PL model with priors specified above and guessing rate per item
mod.vocab <- mirt(d_mat_all, model = mm, itemtype = '3PL')
##
Iteration: 1, Log-Lik: -86297.621, Max-Change: 4.05003
Iteration: 2, Log-Lik: -71565.441, Max-Change: 0.42313
Iteration: 3, Log-Lik: -71055.576, Max-Change: 0.46055
Iteration: 4, Log-Lik: -70646.263, Max-Change: 0.29509
Iteration: 5, Log-Lik: -70319.275, Max-Change: 0.39677
Iteration: 6, Log-Lik: -70028.272, Max-Change: 0.34248
Iteration: 7, Log-Lik: -69804.968, Max-Change: 0.22445
Iteration: 8, Log-Lik: -69636.958, Max-Change: 0.21053
Iteration: 9, Log-Lik: -69508.048, Max-Change: 0.21068
Iteration: 10, Log-Lik: -69406.137, Max-Change: 0.17941
Iteration: 11, Log-Lik: -69327.243, Max-Change: 0.26429
Iteration: 12, Log-Lik: -69266.051, Max-Change: 0.31661
Iteration: 13, Log-Lik: -69219.569, Max-Change: 0.29294
Iteration: 14, Log-Lik: -69184.245, Max-Change: 0.21879
Iteration: 15, Log-Lik: -69157.528, Max-Change: 0.09157
Iteration: 16, Log-Lik: -69137.755, Max-Change: 0.08051
Iteration: 17, Log-Lik: -69122.970, Max-Change: 0.08404
Iteration: 18, Log-Lik: -69111.731, Max-Change: 0.06211
Iteration: 19, Log-Lik: -69103.343, Max-Change: 0.05318
Iteration: 20, Log-Lik: -69097.010, Max-Change: 0.04713
Iteration: 21, Log-Lik: -69092.231, Max-Change: 0.04067
Iteration: 22, Log-Lik: -69088.641, Max-Change: 0.04252
Iteration: 23, Log-Lik: -69085.895, Max-Change: 0.03457
Iteration: 24, Log-Lik: -69083.874, Max-Change: 0.02638
Iteration: 25, Log-Lik: -69078.630, Max-Change: 0.01786
Iteration: 26, Log-Lik: -69078.414, Max-Change: 0.05392
Iteration: 27, Log-Lik: -69078.252, Max-Change: 0.03492
Iteration: 28, Log-Lik: -69078.033, Max-Change: 0.00548
Iteration: 29, Log-Lik: -69077.969, Max-Change: 0.00835
Iteration: 30, Log-Lik: -69077.921, Max-Change: 0.00860
Iteration: 31, Log-Lik: -69077.790, Max-Change: 0.00714
Iteration: 32, Log-Lik: -69077.787, Max-Change: 0.00923
Iteration: 33, Log-Lik: -69077.785, Max-Change: 0.00089
Iteration: 34, Log-Lik: -69077.784, Max-Change: 0.00094
Iteration: 35, Log-Lik: -69077.782, Max-Change: 0.00071
Iteration: 36, Log-Lik: -69077.781, Max-Change: 0.00082
Iteration: 37, Log-Lik: -69077.778, Max-Change: 0.00019
Iteration: 38, Log-Lik: -69077.778, Max-Change: 0.00016
Iteration: 39, Log-Lik: -69077.778, Max-Change: 0.00017
Iteration: 40, Log-Lik: -69077.778, Max-Change: 0.00021
Iteration: 41, Log-Lik: -69077.778, Max-Change: 0.00013
# mirt::marginal_rxx(mod.vocab)
# Error in integrate(fn, lower = -Inf, upper = Inf, mod = mod, den = density, :
# non-finite function value
Create sandbox participant who has seen all trials
list.items <- colnames(d_wide_all %>% select(-pid))
df.sandbox.participant <-
tibble(pid = "sandbox", itemId = list.items, correct = 1)
Create trial ID structure for matrix
duplicate_ids <- school_data_multi_afc %>%
filter(completed==TRUE) %>%
group_by(pid) %>%
summarize(duplicates = length(unique(runId))) %>%
filter(duplicates>1)
df.rocketship.roar.trials.clean <- school_data_multi_afc %>%
filter(pid %in% completed_twice$pid) %>%
filter(!pid %in% duplicate_ids$pid) %>%
filter(completed==TRUE) %>%
filter(studyId %in% c("school-multiAFC"), itemType == "test") %>%
mutate(itemId = paste(targetWord, numAFC, sep = "_")) %>%
dplyr :: distinct(pid, runId, studyId, start_time, numAFC, targetWord, itemId, correct, rt)
df.rocketship.2afc.roar.trials.clean <- school_data_2afc_cleaned %>%
mutate(itemId = paste(word1, numAFC, sep = "_")) %>%
dplyr :: distinct(pid, runId, studyId, start_time, numAFC, word1, word2, itemId, correct, rt)
There were 176 trials in the 2AFC version.
Make each of the data structures for getting thetas
df.multiAFC.resp <- df.rocketship.roar.trials.clean %>%
select(pid, itemId, correct) %>%
rbind(df.sandbox.participant) %>%
unique() %>%
pivot_wider(names_from = itemId, values_from = correct, values_fill = NA)
df.2AFC.resp <- df.rocketship.roar.trials.clean %>%
filter(numAFC == 2) %>%
distinct(pid, itemId, correct) %>%
rbind(df.sandbox.participant) %>%
unique() %>%
pivot_wider(names_from = itemId, values_from = correct, values_fill = NA)
df.3AFC.resp <- df.rocketship.roar.trials.clean %>%
filter(numAFC == 3) %>%
distinct(pid, itemId, correct) %>%
rbind(df.sandbox.participant) %>%
unique() %>%
pivot_wider(names_from = itemId, values_from = correct, values_fill = NA)
df.4AFC.resp <- df.rocketship.roar.trials.clean %>%
filter(numAFC == 4) %>%
distinct(pid, itemId, correct) %>%
rbind(df.sandbox.participant) %>%
unique() %>%
pivot_wider(names_from = itemId, values_from = correct, values_fill = NA)
Make the data structures for getting thetas from 2afc
duplicates_first_admin <- df.rocketship.2afc.roar.trials.clean %>%
group_by(pid) %>%
summarize(duplicates = length(unique(runId))>1) %>%
filter(duplicates==TRUE)
# kids_enough_items <- df.rocketship.2afc.roar.trials.clean %>%
# filter(!pid %in% duplicates_first_admin$pid) %>%
# distinct(pid, itemId, correct) %>%
# filter(itemId %in% list.items) %>%
# group_by(pid) %>%
# summarize(items_per_kid = length(unique(itemId))) %>%
# filter(items_per_kid>20)
df.2AFC.first_admin <- df.rocketship.2afc.roar.trials.clean %>%
filter(!pid %in% duplicates_first_admin$pid) %>%
# filter(pid %in% kids_enough_items$pid) %>%
distinct(pid, itemId, correct) %>%
filter(itemId %in% list.items) %>%
rbind(df.sandbox.participant) %>%
unique() %>%
pivot_wider(names_from = itemId, values_from = correct, values_fill = NA)
getTheta <- function(df){
thetas <- fscores(mod.vocab, method = "ML", response.pattern = df %>% select(-pid), max_theta = 6, min_theta = -8, theta_lim = c(-8, 6))
return (data.frame(thetas)$F)
}
Overall and based on 234afc separately
df.thetas.students <- df.multiAFC.resp %>%
dplyr :: select(pid) %>%
add_column(theta.multiAFC = getTheta(df.multiAFC.resp),
theta.2AFC = getTheta(df.2AFC.resp),
theta.3AFC = getTheta(df.3AFC.resp),
theta.4AFC = getTheta(df.4AFC.resp)
)
Overall and based on 234afc separately
df.thetas.students.first_admin <- df.2AFC.first_admin %>%
dplyr :: select(pid) %>%
add_column(theta.2AFC = getTheta(df.2AFC.first_admin))
dfs_all <- df.thetas.students.first_admin %>%
rename(theta.2AFC.initial = theta.2AFC) %>%
right_join(df.thetas.students, by=c('pid')) %>%
left_join(pid_by_age) %>%
mutate(age_group = as.numeric(age_group)) %>%
filter(!is.na(age_group))
Students who did both multiAFC and 2AFC assessments.
ggplot(data=dfs_all, aes(x=theta.2AFC.initial, y=theta.multiAFC, col=age_group)) +
geom_point(alpha=.6) +
stat_cor(cor.coef.name = 'r', aes(label = ..r.label..)) +
geom_smooth(span=20) +
facet_grid(~age_group) +
theme(legend.position='none')
ggplot(data=dfs_all, aes(x=age_numeric, y=theta.2AFC, col=age_group)) +
geom_point(alpha=.6) +
stat_cor(cor.coef.name = 'r', aes(label = ..r.label..)) +
geom_smooth(span=20) +
# facet_grid(~age_group) +
theme(legend.position='none')
ggplot(data=dfs_all, aes(x=age_numeric, y=theta.multiAFC, col=age_group)) +
geom_point(alpha=.6) +
stat_cor(cor.coef.name = 'r', aes(label = ..r.label..)) +
geom_smooth(span=20) +
# facet_grid(~age_group) +
theme(legend.position='none')
TBH I don’t really understand why some kids are getting such a bad theta estimate despite have
low_theta <- dfs_all %>%
filter(theta.multiAFC< -4) %>%
mutate(age_group = as.numeric(age_group)) %>%
left_join(correct_by_pid_by_afc, by=c('pid'))
ggplot(data=low_theta, aes(x=theta.multiAFC, y=prop_correct_nafc, col=numAFC)) +
geom_point(alpha=.6) +
stat_cor(cor.coef.name = 'r', aes(label = ..r.label..)) +
geom_smooth(span=20) +
facet_grid(~numAFC) +
theme(legend.position='none') +
ggtitle('low theta kids')